@webiny/ai-powerups 6.6.0-alpha.7 → 6.6.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/presentation/modelRoles.js +2 -2
- package/admin/presentation/modelRoles.js.map +1 -1
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js +2 -2
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js.map +1 -1
- package/api/features/AiImageEnrichment/EnrichmentCapabilityDisabledError.d.ts +9 -0
- package/api/features/AiImageEnrichment/EnrichmentCapabilityDisabledError.js +8 -0
- package/api/features/AiImageEnrichment/EnrichmentCapabilityDisabledError.js.map +1 -0
- package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js +8 -5
- package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js.map +1 -1
- package/api/features/AiImageEnrichment/errors.d.ts +3 -1
- package/api/features/AiImageEnrichment/errors.js +1 -0
- package/api/features/AiImageEnrichment/errors.js.map +1 -1
- package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.js +2 -1
- package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.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 +24 -24
|
@@ -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"}
|
|
@@ -2,7 +2,7 @@ import { TaskDefinition, TaskHandler } from "@webiny/api-core/features/task/Task
|
|
|
2
2
|
import { Ai } from "@webiny/api-core/features/ai/index.js";
|
|
3
3
|
import { ApplyImageEnrichmentUseCase, PrepareImageEnrichmentUseCase } from "./abstractions.js";
|
|
4
4
|
import { buildEnrichmentAiRequest } from "./buildEnrichmentAiRequest.js";
|
|
5
|
-
import { EnrichmentNotAnImageError } from "./errors.js";
|
|
5
|
+
import { EnrichmentCapabilityDisabledError, EnrichmentNotAnImageError } from "./errors.js";
|
|
6
6
|
const AI_IMAGE_ENRICHMENT_TASK_ID = "fmAiImageEnrichment";
|
|
7
7
|
class AiImageEnrichmentTaskHandlerImpl {
|
|
8
8
|
constructor(prepare, apply, ai){
|
|
@@ -15,7 +15,7 @@ class AiImageEnrichmentTaskHandlerImpl {
|
|
|
15
15
|
const preparedResult = await this.prepare.execute(input.fileId);
|
|
16
16
|
if (preparedResult.isFail()) {
|
|
17
17
|
const error = preparedResult.error;
|
|
18
|
-
if (error instanceof EnrichmentNotAnImageError) return controller.response.done(error.message);
|
|
18
|
+
if (error instanceof EnrichmentNotAnImageError || error instanceof EnrichmentCapabilityDisabledError) return controller.response.done(error.message);
|
|
19
19
|
return controller.response.error({
|
|
20
20
|
message: error.message
|
|
21
21
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentTask.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentTask.ts"],"sourcesContent":["import {\n TaskDefinition,\n TaskHandler\n} from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { ApplyImageEnrichmentUseCase, PrepareImageEnrichmentUseCase } from \"./abstractions.js\";\nimport { buildEnrichmentAiRequest } from \"./buildEnrichmentAiRequest.js\";\nimport { EnrichmentNotAnImageError } from \"./errors.js\";\n\nexport const AI_IMAGE_ENRICHMENT_TASK_ID = \"fmAiImageEnrichment\";\n\nexport interface IAiImageEnrichmentTaskInput {\n fileId: string;\n}\n\n/**\n * Background enrichment, triggered after a file is created. Shares its preparation and persistence\n * with the streaming HTTP route (`AiImageEnrichmentStreamRoute`); the only difference is that this\n * one waits for the whole AI response, because a background task has no one to stream to.\n */\nclass AiImageEnrichmentTaskHandlerImpl implements TaskHandler.Interface<IAiImageEnrichmentTaskInput> {\n constructor(\n private prepare: PrepareImageEnrichmentUseCase.Interface,\n private apply: ApplyImageEnrichmentUseCase.Interface,\n private ai: Ai.Interface\n ) {}\n\n async run({\n input,\n controller\n }: TaskHandler.RunParams<IAiImageEnrichmentTaskInput>): Promise<\n TaskDefinition.Result<IAiImageEnrichmentTaskInput>\n > {\n if (controller.runtime.isAborted()) {\n return controller.response.aborted();\n }\n\n const preparedResult = await this.prepare.execute(input.fileId);\n if (preparedResult.isFail()) {\n const error = preparedResult.error;\n
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentTask.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentTask.ts"],"sourcesContent":["import {\n TaskDefinition,\n TaskHandler\n} from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { ApplyImageEnrichmentUseCase, PrepareImageEnrichmentUseCase } from \"./abstractions.js\";\nimport { buildEnrichmentAiRequest } from \"./buildEnrichmentAiRequest.js\";\nimport { EnrichmentCapabilityDisabledError } from \"./errors.js\";\nimport { EnrichmentNotAnImageError } from \"./errors.js\";\n\nexport const AI_IMAGE_ENRICHMENT_TASK_ID = \"fmAiImageEnrichment\";\n\nexport interface IAiImageEnrichmentTaskInput {\n fileId: string;\n}\n\n/**\n * Background enrichment, triggered after a file is created. Shares its preparation and persistence\n * with the streaming HTTP route (`AiImageEnrichmentStreamRoute`); the only difference is that this\n * one waits for the whole AI response, because a background task has no one to stream to.\n */\nclass AiImageEnrichmentTaskHandlerImpl implements TaskHandler.Interface<IAiImageEnrichmentTaskInput> {\n constructor(\n private prepare: PrepareImageEnrichmentUseCase.Interface,\n private apply: ApplyImageEnrichmentUseCase.Interface,\n private ai: Ai.Interface\n ) {}\n\n async run({\n input,\n controller\n }: TaskHandler.RunParams<IAiImageEnrichmentTaskInput>): Promise<\n TaskDefinition.Result<IAiImageEnrichmentTaskInput>\n > {\n if (controller.runtime.isAborted()) {\n return controller.response.aborted();\n }\n\n const preparedResult = await this.prepare.execute(input.fileId);\n if (preparedResult.isFail()) {\n const error = preparedResult.error;\n /*\n * Neither of these is a failure. A non-image has nothing to enrich, and a switched-off\n * capability is a setting someone chose. Reporting either as an error would log one on\n * every upload. A real misconfiguration still falls through to the error below.\n */\n if (\n error instanceof EnrichmentNotAnImageError ||\n error instanceof EnrichmentCapabilityDisabledError\n ) {\n return controller.response.done(error.message);\n }\n return controller.response.error({ message: error.message });\n }\n\n const prepared = preparedResult.value;\n\n let tags: string[];\n let description: string;\n try {\n const request = buildEnrichmentAiRequest(prepared);\n const aiResult = await this.ai.generateText(request);\n\n tags = aiResult.output.tags;\n description = aiResult.output.description;\n } catch (error) {\n return controller.response.error({\n message: `AI enrichment failed: ${error instanceof Error ? error.message : String(error)}`\n });\n }\n\n const appliedResult = await this.apply.execute({\n fileId: prepared.fileId,\n tags,\n description\n });\n\n if (appliedResult.isFail()) {\n return controller.response.error({ message: appliedResult.error.message });\n }\n\n return controller.response.done(\"AI image enrichment completed successfully.\");\n }\n}\n\nconst AiImageEnrichmentTaskHandler = TaskHandler.createImplementation({\n implementation: AiImageEnrichmentTaskHandlerImpl,\n dependencies: [PrepareImageEnrichmentUseCase, ApplyImageEnrichmentUseCase, Ai]\n});\n\nclass AiImageEnrichmentTaskImpl implements TaskDefinition.Interface {\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 public readonly selfCleanup = [\"onSuccess\" as const, \"onAbort\" as const];\n\n handler = AiImageEnrichmentTaskHandler;\n}\n\nexport const AiImageEnrichmentTask = TaskDefinition.createImplementation({\n implementation: AiImageEnrichmentTaskImpl,\n dependencies: []\n});\n"],"names":["AI_IMAGE_ENRICHMENT_TASK_ID","AiImageEnrichmentTaskHandlerImpl","prepare","apply","ai","input","controller","preparedResult","error","EnrichmentNotAnImageError","EnrichmentCapabilityDisabledError","prepared","tags","description","request","buildEnrichmentAiRequest","aiResult","Error","String","appliedResult","AiImageEnrichmentTaskHandler","TaskHandler","PrepareImageEnrichmentUseCase","ApplyImageEnrichmentUseCase","Ai","AiImageEnrichmentTaskImpl","AiImageEnrichmentTask","TaskDefinition"],"mappings":";;;;;AAUO,MAAMA,8BAA8B;AAW3C,MAAMC;IACF,YACYC,OAAgD,EAChDC,KAA4C,EAC5CC,EAAgB,CAC1B;aAHUF,OAAO,GAAPA;aACAC,KAAK,GAALA;aACAC,EAAE,GAAFA;IACT;IAEH,MAAM,IAAI,EACNC,KAAK,EACLC,UAAU,EACuC,EAEnD;QACE,IAAIA,WAAW,OAAO,CAAC,SAAS,IAC5B,OAAOA,WAAW,QAAQ,CAAC,OAAO;QAGtC,MAAMC,iBAAiB,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAACF,MAAM,MAAM;QAC9D,IAAIE,eAAe,MAAM,IAAI;YACzB,MAAMC,QAAQD,eAAe,KAAK;YAMlC,IACIC,iBAAiBC,6BACjBD,iBAAiBE,mCAEjB,OAAOJ,WAAW,QAAQ,CAAC,IAAI,CAACE,MAAM,OAAO;YAEjD,OAAOF,WAAW,QAAQ,CAAC,KAAK,CAAC;gBAAE,SAASE,MAAM,OAAO;YAAC;QAC9D;QAEA,MAAMG,WAAWJ,eAAe,KAAK;QAErC,IAAIK;QACJ,IAAIC;QACJ,IAAI;YACA,MAAMC,UAAUC,yBAAyBJ;YACzC,MAAMK,WAAW,MAAM,IAAI,CAAC,EAAE,CAAC,YAAY,CAACF;YAE5CF,OAAOI,SAAS,MAAM,CAAC,IAAI;YAC3BH,cAAcG,SAAS,MAAM,CAAC,WAAW;QAC7C,EAAE,OAAOR,OAAO;YACZ,OAAOF,WAAW,QAAQ,CAAC,KAAK,CAAC;gBAC7B,SAAS,CAAC,sBAAsB,EAAEE,iBAAiBS,QAAQT,MAAM,OAAO,GAAGU,OAAOV,QAAQ;YAC9F;QACJ;QAEA,MAAMW,gBAAgB,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAC3C,QAAQR,SAAS,MAAM;YACvBC;YACAC;QACJ;QAEA,IAAIM,cAAc,MAAM,IACpB,OAAOb,WAAW,QAAQ,CAAC,KAAK,CAAC;YAAE,SAASa,cAAc,KAAK,CAAC,OAAO;QAAC;QAG5E,OAAOb,WAAW,QAAQ,CAAC,IAAI,CAAC;IACpC;AACJ;AAEA,MAAMc,+BAA+BC,YAAY,oBAAoB,CAAC;IAClE,gBAAgBpB;IAChB,cAAc;QAACqB;QAA+BC;QAA6BC;KAAG;AAClF;AAEA,MAAMC;;aACF,EAAE,GAAGzB;aACL,KAAK,GAAG;aACR,WAAW,GAAG;aACd,aAAa,GAAG;aAChB,SAAS,GAAG;aACZ,YAAY,GAAG;aACC,WAAW,GAAG;YAAC;YAAsB;SAAmB;aAExE,OAAO,GAAGoB;;AACd;AAEO,MAAMM,wBAAwBC,eAAe,oBAAoB,CAAC;IACrE,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image enrichment is switched off in settings. Not a failure: someone chose it, so the upload task
|
|
3
|
+
* finishes as done, the same as for a file that is not an image. Kept apart from
|
|
4
|
+
* `EnrichmentNoProviderError`, which is a real misconfiguration and should stay loud.
|
|
5
|
+
*/
|
|
6
|
+
export declare class EnrichmentCapabilityDisabledError extends Error {
|
|
7
|
+
readonly code: "ENRICHMENT_CAPABILITY_DISABLED";
|
|
8
|
+
constructor(reason: string);
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/EnrichmentCapabilityDisabledError.js","sources":["../../../../src/api/features/AiImageEnrichment/EnrichmentCapabilityDisabledError.ts"],"sourcesContent":["/**\n * Image enrichment is switched off in settings. Not a failure: someone chose it, so the upload task\n * finishes as done, the same as for a file that is not an image. Kept apart from\n * `EnrichmentNoProviderError`, which is a real misconfiguration and should stay loud.\n */\nexport class EnrichmentCapabilityDisabledError extends Error {\n readonly code = \"ENRICHMENT_CAPABILITY_DISABLED\" as const;\n\n constructor(reason: string) {\n super(reason);\n }\n}\n"],"names":["EnrichmentCapabilityDisabledError","Error","reason"],"mappings":"AAKO,MAAMA,0CAA0CC;IAGnD,YAAYC,MAAc,CAAE;QACxB,KAAK,CAACA,SAAAA,IAAAA,CAHD,IAAI,GAAG;IAIhB;AACJ"}
|
|
@@ -2,9 +2,9 @@ import { Result } from "@webiny/feature/api";
|
|
|
2
2
|
import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
|
|
3
3
|
import { GetFileContentsByIdUseCase } from "@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js";
|
|
4
4
|
import { PrepareImageEnrichmentUseCase } from "./abstractions.js";
|
|
5
|
-
import { ResolveAiCapabilityUseCase, withAdditionalInstructions } from "../Capabilities/index.js";
|
|
5
|
+
import { AiCapabilityDisabledError, ResolveAiCapabilityUseCase, withAdditionalInstructions } from "../Capabilities/index.js";
|
|
6
6
|
import { FM_IMAGE_ENRICHMENT_CAPABILITY } from "./capability.js";
|
|
7
|
-
import { EnrichmentFileContentsError, EnrichmentFileNotFoundError, EnrichmentNoProviderError, EnrichmentNotAnImageError } from "./errors.js";
|
|
7
|
+
import { EnrichmentCapabilityDisabledError, EnrichmentFileContentsError, EnrichmentFileNotFoundError, EnrichmentNoProviderError, EnrichmentNotAnImageError } from "./errors.js";
|
|
8
8
|
class PrepareImageEnrichmentUseCaseImpl {
|
|
9
9
|
constructor(getFile, getFileContents, resolveCapability){
|
|
10
10
|
this.getFile = getFile;
|
|
@@ -16,11 +16,14 @@ class PrepareImageEnrichmentUseCaseImpl {
|
|
|
16
16
|
if (fileResult.isFail()) return Result.fail(new EnrichmentFileNotFoundError(fileId));
|
|
17
17
|
const file = fileResult.value;
|
|
18
18
|
if (!file.type.startsWith("image/")) return Result.fail(new EnrichmentNotAnImageError(file.type));
|
|
19
|
-
const contentsResult = await this.getFileContents.execute(fileId);
|
|
20
|
-
if (contentsResult.isFail()) return Result.fail(new EnrichmentFileContentsError(contentsResult.error.message));
|
|
21
19
|
const resolved = await this.resolveCapability.execute(FM_IMAGE_ENRICHMENT_CAPABILITY);
|
|
22
|
-
if (resolved.isFail())
|
|
20
|
+
if (resolved.isFail()) {
|
|
21
|
+
if (resolved.error instanceof AiCapabilityDisabledError) return Result.fail(new EnrichmentCapabilityDisabledError(resolved.error.message));
|
|
22
|
+
return Result.fail(new EnrichmentNoProviderError(resolved.error.message));
|
|
23
|
+
}
|
|
23
24
|
const capability = resolved.value;
|
|
25
|
+
const contentsResult = await this.getFileContents.execute(fileId);
|
|
26
|
+
if (contentsResult.isFail()) return Result.fail(new EnrichmentFileContentsError(contentsResult.error.message));
|
|
24
27
|
return Result.ok({
|
|
25
28
|
fileId: file.id,
|
|
26
29
|
imageBase64: contentsResult.value.buffer.toString("base64"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js","sources":["../../../../src/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.ts"],"sourcesContent":["import { Result } from \"@webiny/feature/api\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\nimport { GetFileContentsByIdUseCase } from \"@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js\";\nimport {\n PrepareImageEnrichmentUseCase as UseCaseAbstraction,\n type IPreparedImageEnrichment\n} from \"./abstractions.js\";\nimport {\n ResolveAiCapabilityUseCase,\n withAdditionalInstructions\n} from \"~/api/features/Capabilities/index.js\";\nimport { FM_IMAGE_ENRICHMENT_CAPABILITY } from \"./capability.js\";\nimport {\n EnrichmentFileContentsError,\n EnrichmentFileNotFoundError,\n EnrichmentNoProviderError,\n EnrichmentNotAnImageError\n} from \"./errors.js\";\nimport type { ImageEnrichmentError } from \"./errors.js\";\n\nclass PrepareImageEnrichmentUseCaseImpl implements UseCaseAbstraction.Interface {\n constructor(\n private getFile: GetFileUseCase.Interface,\n private getFileContents: GetFileContentsByIdUseCase.Interface,\n private resolveCapability: ResolveAiCapabilityUseCase.Interface\n ) {}\n\n async execute(fileId: string): Promise<Result<IPreparedImageEnrichment, ImageEnrichmentError>> {\n const fileResult = await this.getFile.execute(fileId);\n if (fileResult.isFail()) {\n return Result.fail(new EnrichmentFileNotFoundError(fileId));\n }\n\n const file = fileResult.value;\n\n if (!file.type.startsWith(\"image/\")) {\n return Result.fail(new EnrichmentNotAnImageError(file.type));\n }\n\n // Read the image bytes and send them to the AI as base64, NOT a URL. A URL forces the\n // provider to fetch the file — which fails for private/access-controlled files, leaks the\n // domain, and can't reach a non-public origin (e.g. local dev). base64 is a portable standard\n // that works on every setup. (Also resolves the AI-SDK \"image part\" deprecation.)\n const contentsResult = await this.getFileContents.execute(fileId);\n if (contentsResult.isFail()) {\n return Result.fail(new EnrichmentFileContentsError(contentsResult.error.message));\n }\n\n
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js","sources":["../../../../src/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.ts"],"sourcesContent":["import { Result } from \"@webiny/feature/api\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\nimport { GetFileContentsByIdUseCase } from \"@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js\";\nimport {\n PrepareImageEnrichmentUseCase as UseCaseAbstraction,\n type IPreparedImageEnrichment\n} from \"./abstractions.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\";\nimport { EnrichmentCapabilityDisabledError } from \"./errors.js\";\nimport {\n EnrichmentFileContentsError,\n EnrichmentFileNotFoundError,\n EnrichmentNoProviderError,\n EnrichmentNotAnImageError\n} from \"./errors.js\";\nimport type { ImageEnrichmentError } from \"./errors.js\";\n\nclass PrepareImageEnrichmentUseCaseImpl implements UseCaseAbstraction.Interface {\n constructor(\n private getFile: GetFileUseCase.Interface,\n private getFileContents: GetFileContentsByIdUseCase.Interface,\n private resolveCapability: ResolveAiCapabilityUseCase.Interface\n ) {}\n\n async execute(fileId: string): Promise<Result<IPreparedImageEnrichment, ImageEnrichmentError>> {\n const fileResult = await this.getFile.execute(fileId);\n if (fileResult.isFail()) {\n return Result.fail(new EnrichmentFileNotFoundError(fileId));\n }\n\n const file = fileResult.value;\n\n if (!file.type.startsWith(\"image/\")) {\n return Result.fail(new EnrichmentNotAnImageError(file.type));\n }\n\n /*\n * Resolved before the download, not after. Switched off is now a quiet, normal outcome, so\n * checking it last would pull the whole image from storage on every upload only to throw it\n * away, with nothing in the logs to show for it.\n */\n const resolved = await this.resolveCapability.execute(FM_IMAGE_ENRICHMENT_CAPABILITY);\n if (resolved.isFail()) {\n /*\n * Switched off is a decision, not a missing model, so it must not become the error\n * that callers treat as a misconfiguration.\n */\n if (resolved.error instanceof AiCapabilityDisabledError) {\n return Result.fail(new EnrichmentCapabilityDisabledError(resolved.error.message));\n }\n return Result.fail(new EnrichmentNoProviderError(resolved.error.message));\n }\n\n const capability = resolved.value;\n\n // Read the image bytes and send them to the AI as base64, NOT a URL. A URL forces the\n // provider to fetch the file — which fails for private/access-controlled files, leaks the\n // domain, and can't reach a non-public origin (e.g. local dev). base64 is a portable standard\n // that works on every setup. (Also resolves the AI-SDK \"image part\" deprecation.)\n const contentsResult = await this.getFileContents.execute(fileId);\n if (contentsResult.isFail()) {\n return Result.fail(new EnrichmentFileContentsError(contentsResult.error.message));\n }\n\n return Result.ok({\n fileId: file.id,\n imageBase64: contentsResult.value.buffer.toString(\"base64\"),\n imageMediaType: contentsResult.value.contentType,\n model: capability.model,\n prompt: withAdditionalInstructions(capability),\n connection: capability.connection\n });\n }\n}\n\nexport const PrepareImageEnrichmentUseCase = UseCaseAbstraction.createImplementation({\n implementation: PrepareImageEnrichmentUseCaseImpl,\n dependencies: [GetFileUseCase, GetFileContentsByIdUseCase, ResolveAiCapabilityUseCase]\n});\n"],"names":["PrepareImageEnrichmentUseCaseImpl","getFile","getFileContents","resolveCapability","fileId","fileResult","Result","EnrichmentFileNotFoundError","file","EnrichmentNotAnImageError","resolved","FM_IMAGE_ENRICHMENT_CAPABILITY","AiCapabilityDisabledError","EnrichmentCapabilityDisabledError","EnrichmentNoProviderError","capability","contentsResult","EnrichmentFileContentsError","withAdditionalInstructions","PrepareImageEnrichmentUseCase","UseCaseAbstraction","GetFileUseCase","GetFileContentsByIdUseCase","ResolveAiCapabilityUseCase"],"mappings":";;;;;;;AAsBA,MAAMA;IACF,YACYC,OAAiC,EACjCC,eAAqD,EACrDC,iBAAuD,CACjE;aAHUF,OAAO,GAAPA;aACAC,eAAe,GAAfA;aACAC,iBAAiB,GAAjBA;IACT;IAEH,MAAM,QAAQC,MAAc,EAAmE;QAC3F,MAAMC,aAAa,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAACD;QAC9C,IAAIC,WAAW,MAAM,IACjB,OAAOC,OAAO,IAAI,CAAC,IAAIC,4BAA4BH;QAGvD,MAAMI,OAAOH,WAAW,KAAK;QAE7B,IAAI,CAACG,KAAK,IAAI,CAAC,UAAU,CAAC,WACtB,OAAOF,OAAO,IAAI,CAAC,IAAIG,0BAA0BD,KAAK,IAAI;QAQ9D,MAAME,WAAW,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAACC;QACtD,IAAID,SAAS,MAAM,IAAI;YAKnB,IAAIA,SAAS,KAAK,YAAYE,2BAC1B,OAAON,OAAO,IAAI,CAAC,IAAIO,kCAAkCH,SAAS,KAAK,CAAC,OAAO;YAEnF,OAAOJ,OAAO,IAAI,CAAC,IAAIQ,0BAA0BJ,SAAS,KAAK,CAAC,OAAO;QAC3E;QAEA,MAAMK,aAAaL,SAAS,KAAK;QAMjC,MAAMM,iBAAiB,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAACZ;QAC1D,IAAIY,eAAe,MAAM,IACrB,OAAOV,OAAO,IAAI,CAAC,IAAIW,4BAA4BD,eAAe,KAAK,CAAC,OAAO;QAGnF,OAAOV,OAAO,EAAE,CAAC;YACb,QAAQE,KAAK,EAAE;YACf,aAAaQ,eAAe,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;YAClD,gBAAgBA,eAAe,KAAK,CAAC,WAAW;YAChD,OAAOD,WAAW,KAAK;YACvB,QAAQG,2BAA2BH;YACnC,YAAYA,WAAW,UAAU;QACrC;IACJ;AACJ;AAEO,MAAMI,8DAAgCC,8BAAAA,oBAAuC,CAAC;IACjF,gBAAgBpB;IAChB,cAAc;QAACqB;QAAgBC;QAA4BC;KAA2B;AAC1F"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { EnrichmentCapabilityDisabledError } from "./EnrichmentCapabilityDisabledError.js";
|
|
2
|
+
export { EnrichmentCapabilityDisabledError } from "./EnrichmentCapabilityDisabledError.js";
|
|
1
3
|
/**
|
|
2
4
|
* Errors shared by every image-enrichment entry point (the background task and the streaming HTTP
|
|
3
5
|
* route). Each carries a `code` so callers can map it to their own transport: the task turns them
|
|
@@ -28,4 +30,4 @@ export declare class EnrichmentPersistError extends Error {
|
|
|
28
30
|
readonly code: "ENRICHMENT_PERSIST_FAILED";
|
|
29
31
|
constructor(reason: string);
|
|
30
32
|
}
|
|
31
|
-
export type ImageEnrichmentError = EnrichmentFileNotFoundError | EnrichmentNotAnImageError | EnrichmentFileContentsError | EnrichmentNoProviderError | EnrichmentPersistError;
|
|
33
|
+
export type ImageEnrichmentError = EnrichmentFileNotFoundError | EnrichmentNotAnImageError | EnrichmentFileContentsError | EnrichmentNoProviderError | EnrichmentCapabilityDisabledError | EnrichmentPersistError;
|
|
@@ -23,6 +23,7 @@ class EnrichmentPersistError extends Error {
|
|
|
23
23
|
super(`Failed to update file: ${reason}`), this.code = "ENRICHMENT_PERSIST_FAILED";
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
export { EnrichmentCapabilityDisabledError } from "./EnrichmentCapabilityDisabledError.js";
|
|
26
27
|
export { EnrichmentFileContentsError, EnrichmentFileNotFoundError, EnrichmentNoProviderError, EnrichmentNotAnImageError, EnrichmentPersistError };
|
|
27
28
|
|
|
28
29
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/AiImageEnrichment/errors.js","sources":["../../../../src/api/features/AiImageEnrichment/errors.ts"],"sourcesContent":["/**\n * Errors shared by every image-enrichment entry point (the background task and the streaming HTTP\n * route). Each carries a `code` so callers can map it to their own transport: the task turns them\n * into task results, the route into HTTP status codes.\n */\nexport class EnrichmentFileNotFoundError extends Error {\n readonly code = \"ENRICHMENT_FILE_NOT_FOUND\" as const;\n\n constructor(fileId: string) {\n super(`File not found: ${fileId}`);\n }\n}\n\nexport class EnrichmentNotAnImageError extends Error {\n readonly code = \"ENRICHMENT_NOT_AN_IMAGE\" as const;\n\n constructor(type: string) {\n super(`File is not an image (received \"${type}\"); skipping AI enrichment.`);\n }\n}\n\nexport class EnrichmentFileContentsError extends Error {\n readonly code = \"ENRICHMENT_FILE_CONTENTS_UNREADABLE\" as const;\n\n constructor(reason: string) {\n super(`Unable to read file contents: ${reason}`);\n }\n}\n\nexport class EnrichmentNoProviderError extends Error {\n readonly code = \"ENRICHMENT_NO_AI_PROVIDER\" as const;\n\n /**\n * The reason comes from capability resolution, which knows which of the several ways this can\n * fail actually happened: no model in the Vision role, a deleted connection, a missing key.\n * Passing it through beats replacing all of them with one generic sentence.\n */\n constructor(reason?: string) {\n super(\n reason ??\n \"No AI model is configured for image enrichment. Pick one under Settings → AI Power-Ups → Model roles.\"\n );\n }\n}\n\nexport class EnrichmentPersistError extends Error {\n readonly code = \"ENRICHMENT_PERSIST_FAILED\" as const;\n\n constructor(reason: string) {\n super(`Failed to update file: ${reason}`);\n }\n}\n\nexport type ImageEnrichmentError =\n | EnrichmentFileNotFoundError\n | EnrichmentNotAnImageError\n | EnrichmentFileContentsError\n | EnrichmentNoProviderError\n | EnrichmentPersistError;\n"],"names":["EnrichmentFileNotFoundError","Error","fileId","EnrichmentNotAnImageError","type","EnrichmentFileContentsError","reason","EnrichmentNoProviderError","EnrichmentPersistError"],"mappings":"
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/errors.js","sources":["../../../../src/api/features/AiImageEnrichment/errors.ts"],"sourcesContent":["import type { EnrichmentCapabilityDisabledError } from \"./EnrichmentCapabilityDisabledError.js\";\n\n/* Its own file, one class per file, but re-exported so every caller imports errors from here. */\nexport { EnrichmentCapabilityDisabledError } from \"./EnrichmentCapabilityDisabledError.js\";\n\n/**\n * Errors shared by every image-enrichment entry point (the background task and the streaming HTTP\n * route). Each carries a `code` so callers can map it to their own transport: the task turns them\n * into task results, the route into HTTP status codes.\n */\nexport class EnrichmentFileNotFoundError extends Error {\n readonly code = \"ENRICHMENT_FILE_NOT_FOUND\" as const;\n\n constructor(fileId: string) {\n super(`File not found: ${fileId}`);\n }\n}\n\nexport class EnrichmentNotAnImageError extends Error {\n readonly code = \"ENRICHMENT_NOT_AN_IMAGE\" as const;\n\n constructor(type: string) {\n super(`File is not an image (received \"${type}\"); skipping AI enrichment.`);\n }\n}\n\nexport class EnrichmentFileContentsError extends Error {\n readonly code = \"ENRICHMENT_FILE_CONTENTS_UNREADABLE\" as const;\n\n constructor(reason: string) {\n super(`Unable to read file contents: ${reason}`);\n }\n}\n\nexport class EnrichmentNoProviderError extends Error {\n readonly code = \"ENRICHMENT_NO_AI_PROVIDER\" as const;\n\n /**\n * The reason comes from capability resolution, which knows which of the several ways this can\n * fail actually happened: no model in the Vision role, a deleted connection, a missing key.\n * Passing it through beats replacing all of them with one generic sentence.\n */\n constructor(reason?: string) {\n super(\n reason ??\n \"No AI model is configured for image enrichment. Pick one under Settings → AI Power-Ups → Model roles.\"\n );\n }\n}\n\nexport class EnrichmentPersistError extends Error {\n readonly code = \"ENRICHMENT_PERSIST_FAILED\" as const;\n\n constructor(reason: string) {\n super(`Failed to update file: ${reason}`);\n }\n}\n\nexport type ImageEnrichmentError =\n | EnrichmentFileNotFoundError\n | EnrichmentNotAnImageError\n | EnrichmentFileContentsError\n | EnrichmentNoProviderError\n | EnrichmentCapabilityDisabledError\n | EnrichmentPersistError;\n"],"names":["EnrichmentFileNotFoundError","Error","fileId","EnrichmentNotAnImageError","type","EnrichmentFileContentsError","reason","EnrichmentNoProviderError","EnrichmentPersistError"],"mappings":"AAUO,MAAMA,oCAAoCC;IAG7C,YAAYC,MAAc,CAAE;QACxB,KAAK,CAAC,CAAC,gBAAgB,EAAEA,QAAQ,QAH5B,IAAI,GAAG;IAIhB;AACJ;AAEO,MAAMC,kCAAkCF;IAG3C,YAAYG,IAAY,CAAE;QACtB,KAAK,CAAC,CAAC,gCAAgC,EAAEA,KAAK,2BAA2B,CAAC,QAHrE,IAAI,GAAG;IAIhB;AACJ;AAEO,MAAMC,oCAAoCJ;IAG7C,YAAYK,MAAc,CAAE;QACxB,KAAK,CAAC,CAAC,8BAA8B,EAAEA,QAAQ,QAH1C,IAAI,GAAG;IAIhB;AACJ;AAEO,MAAMC,kCAAkCN;IAQ3C,YAAYK,MAAe,CAAE;QACzB,KAAK,CACDA,UACI,+GAVH,IAAI,GAAG;IAYhB;AACJ;AAEO,MAAME,+BAA+BP;IAGxC,YAAYK,MAAc,CAAE;QACxB,KAAK,CAAC,CAAC,uBAAuB,EAAEA,QAAQ,QAHnC,IAAI,GAAG;IAIhB;AACJ"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { EnrichmentFileNotFoundError, EnrichmentNotAnImageError } from "./errors.js";
|
|
1
|
+
import { EnrichmentCapabilityDisabledError, EnrichmentFileNotFoundError, EnrichmentNotAnImageError } from "./errors.js";
|
|
2
2
|
function imageEnrichmentErrorStatusCode(error) {
|
|
3
3
|
if (error instanceof EnrichmentFileNotFoundError) return 404;
|
|
4
4
|
if (error instanceof EnrichmentNotAnImageError) return 400;
|
|
5
|
+
if (error instanceof EnrichmentCapabilityDisabledError) return 409;
|
|
5
6
|
return 500;
|
|
6
7
|
}
|
|
7
8
|
export { imageEnrichmentErrorStatusCode };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.js","sources":["../../../../src/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.ts"],"sourcesContent":["import { EnrichmentFileNotFoundError, EnrichmentNotAnImageError } from \"./errors.js\";\nimport type { ImageEnrichmentError } from \"./errors.js\";\n\n/**\n * Maps an enrichment failure to the HTTP status a transport should answer with. Lives next to the\n * errors rather than in a route, so a second entry point can't invent a different mapping.\n */\nexport function imageEnrichmentErrorStatusCode(error: ImageEnrichmentError): number {\n if (error instanceof EnrichmentFileNotFoundError) {\n return 404;\n }\n if (error instanceof EnrichmentNotAnImageError) {\n return 400;\n }\n return 500;\n}\n"],"names":["imageEnrichmentErrorStatusCode","error","EnrichmentFileNotFoundError","EnrichmentNotAnImageError"],"mappings":";
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.js","sources":["../../../../src/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.ts"],"sourcesContent":["import { EnrichmentFileNotFoundError, EnrichmentNotAnImageError } from \"./errors.js\";\nimport { EnrichmentCapabilityDisabledError } from \"./errors.js\";\nimport type { ImageEnrichmentError } from \"./errors.js\";\n\n/**\n * Maps an enrichment failure to the HTTP status a transport should answer with. Lives next to the\n * errors rather than in a route, so a second entry point can't invent a different mapping.\n */\nexport function imageEnrichmentErrorStatusCode(error: ImageEnrichmentError): number {\n if (error instanceof EnrichmentFileNotFoundError) {\n return 404;\n }\n if (error instanceof EnrichmentNotAnImageError) {\n return 400;\n }\n /*\n * The request is fine and the server is fine; the feature is switched off. A 500 would tell\n * whoever called it that something broke, which is the one thing that did not happen.\n */\n if (error instanceof EnrichmentCapabilityDisabledError) {\n return 409;\n }\n return 500;\n}\n"],"names":["imageEnrichmentErrorStatusCode","error","EnrichmentFileNotFoundError","EnrichmentNotAnImageError","EnrichmentCapabilityDisabledError"],"mappings":";AAQO,SAASA,+BAA+BC,KAA2B;IACtE,IAAIA,iBAAiBC,6BACjB,OAAO;IAEX,IAAID,iBAAiBE,2BACjB,OAAO;IAMX,IAAIF,iBAAiBG,mCACjB,OAAO;IAEX,OAAO;AACX"}
|
|
@@ -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
|
class WbTranslatePageDecoratorImpl {
|
|
@@ -39,6 +39,10 @@ class WbTranslatePageDecoratorImpl {
|
|
|
39
39
|
async translate(page, sourceLanguage, targetLanguage) {
|
|
40
40
|
const resolved = await this.resolveCapability.execute(WB_TRANSLATE_PAGE_CAPABILITY);
|
|
41
41
|
if (resolved.isFail()) {
|
|
42
|
+
if (resolved.error instanceof AiCapabilityDisabledError) {
|
|
43
|
+
this.logger.info("AI page translation is switched off; skipping.");
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
42
46
|
this.logger.warn({
|
|
43
47
|
reason: resolved.error.message
|
|
44
48
|
}, "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\";\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 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 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\n * non-fatal: the page is already created, just untranslated. It does get logged\n * now, because a silently skipped translation was indistinguishable from a\n * 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 JSON.parse(result.text);\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 ResolveAiCapabilityUseCase,\n Logger,\n Ai,\n LexicalParser,\n UpdatePageRepository\n ]\n});\n"],"names":["WbTranslatePageDecoratorImpl","getDefaultLanguage","resolveCapability","logger","ai","lexicalParser","updatePageRepository","decoratee","params","result","page","sourceLanguage","translations","Result","updateData","updateResult","targetLanguage","resolved","WB_TRANSLATE_PAGE_CAPABILITY","capability","properties","bindings","input","JSON","withAdditionalInstructions","elementId","element","inputs","inputName","text","structuredClone","translatedPathSlug","key","value","Object","slug","languageCode","binding","state","set","WbTranslatePageDecorator","TranslatePageUseCase","GetDefaultLanguageUseCase","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\";\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 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 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\n * non-fatal: the page is already created, just untranslated. A misconfiguration is\n * logged as a warning, because a silently skipped translation was indistinguishable\n * from a broken 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 JSON.parse(result.text);\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 ResolveAiCapabilityUseCase,\n Logger,\n Ai,\n LexicalParser,\n UpdatePageRepository\n ]\n});\n"],"names":["WbTranslatePageDecoratorImpl","getDefaultLanguage","resolveCapability","logger","ai","lexicalParser","updatePageRepository","decoratee","params","result","page","sourceLanguage","translations","Result","updateData","updateResult","targetLanguage","resolved","WB_TRANSLATE_PAGE_CAPABILITY","AiCapabilityDisabledError","capability","properties","bindings","input","JSON","withAdditionalInstructions","elementId","element","inputs","inputName","text","structuredClone","translatedPathSlug","key","value","Object","slug","languageCode","binding","state","set","WbTranslatePageDecorator","TranslatePageUseCase","GetDefaultLanguageUseCase","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.6.0-alpha.
|
|
3
|
+
"version": "6.6.0-alpha.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -18,26 +18,26 @@
|
|
|
18
18
|
"author": "Webiny Ltd.",
|
|
19
19
|
"license": "Webiny Enterprise",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@webiny/admin-ui": "6.6.0-alpha.
|
|
22
|
-
"@webiny/api-core": "6.6.0-alpha.
|
|
23
|
-
"@webiny/api-file-manager": "6.6.0-alpha.
|
|
24
|
-
"@webiny/api-headless-cms": "6.6.0-alpha.
|
|
25
|
-
"@webiny/api-website-builder": "6.6.0-alpha.
|
|
26
|
-
"@webiny/api-websockets": "6.6.0-alpha.
|
|
27
|
-
"@webiny/app": "6.6.0-alpha.
|
|
28
|
-
"@webiny/app-admin": "6.6.0-alpha.
|
|
29
|
-
"@webiny/app-headless-cms": "6.6.0-alpha.
|
|
30
|
-
"@webiny/app-website-builder": "6.6.0-alpha.
|
|
31
|
-
"@webiny/app-websockets": "6.6.0-alpha.
|
|
32
|
-
"@webiny/background-tasks": "6.6.0-alpha.
|
|
21
|
+
"@webiny/admin-ui": "6.6.0-alpha.8",
|
|
22
|
+
"@webiny/api-core": "6.6.0-alpha.8",
|
|
23
|
+
"@webiny/api-file-manager": "6.6.0-alpha.8",
|
|
24
|
+
"@webiny/api-headless-cms": "6.6.0-alpha.8",
|
|
25
|
+
"@webiny/api-website-builder": "6.6.0-alpha.8",
|
|
26
|
+
"@webiny/api-websockets": "6.6.0-alpha.8",
|
|
27
|
+
"@webiny/app": "6.6.0-alpha.8",
|
|
28
|
+
"@webiny/app-admin": "6.6.0-alpha.8",
|
|
29
|
+
"@webiny/app-headless-cms": "6.6.0-alpha.8",
|
|
30
|
+
"@webiny/app-website-builder": "6.6.0-alpha.8",
|
|
31
|
+
"@webiny/app-websockets": "6.6.0-alpha.8",
|
|
32
|
+
"@webiny/background-tasks": "6.6.0-alpha.8",
|
|
33
33
|
"@webiny/di": "1.1.0",
|
|
34
|
-
"@webiny/event-handler-core": "6.6.0-alpha.
|
|
35
|
-
"@webiny/feature": "6.6.0-alpha.
|
|
36
|
-
"@webiny/icons": "6.6.0-alpha.
|
|
37
|
-
"@webiny/languages": "6.6.0-alpha.
|
|
38
|
-
"@webiny/lexical-converter": "6.6.0-alpha.
|
|
39
|
-
"@webiny/project-aws": "6.6.0-alpha.
|
|
40
|
-
"@webiny/utils": "6.6.0-alpha.
|
|
34
|
+
"@webiny/event-handler-core": "6.6.0-alpha.8",
|
|
35
|
+
"@webiny/feature": "6.6.0-alpha.8",
|
|
36
|
+
"@webiny/icons": "6.6.0-alpha.8",
|
|
37
|
+
"@webiny/languages": "6.6.0-alpha.8",
|
|
38
|
+
"@webiny/lexical-converter": "6.6.0-alpha.8",
|
|
39
|
+
"@webiny/project-aws": "6.6.0-alpha.8",
|
|
40
|
+
"@webiny/utils": "6.6.0-alpha.8",
|
|
41
41
|
"ai": "7.0.73",
|
|
42
42
|
"front-matter": "4.0.2",
|
|
43
43
|
"happy-dom": "20.11.6",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"zod": "4.4.3"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@webiny/api-graphql": "6.6.0-alpha.
|
|
53
|
-
"@webiny/build-tools": "6.6.0-alpha.
|
|
54
|
-
"@webiny/feature-flags": "6.6.0-alpha.
|
|
55
|
-
"@webiny/wcp": "6.6.0-alpha.
|
|
52
|
+
"@webiny/api-graphql": "6.6.0-alpha.8",
|
|
53
|
+
"@webiny/build-tools": "6.6.0-alpha.8",
|
|
54
|
+
"@webiny/feature-flags": "6.6.0-alpha.8",
|
|
55
|
+
"@webiny/wcp": "6.6.0-alpha.8",
|
|
56
56
|
"rimraf": "6.1.3",
|
|
57
57
|
"typescript": "7.0.2",
|
|
58
58
|
"vitest": "4.1.11"
|