@webiny/ai-powerups 6.6.0-alpha.0 → 6.6.0-alpha.2
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 +2 -2
- package/AiPowerups.js.map +1 -1
- package/admin/Extension.js +10 -2
- package/admin/Extension.js.map +1 -1
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsGateway.d.ts +12 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsGateway.js +46 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsGateway.js.map +1 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsRepository.d.ts +11 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsRepository.js +18 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsRepository.js.map +1 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsUseCase.d.ts +11 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsUseCase.js +18 -0
- package/admin/features/compareEntryRevisions/CompareEntryRevisionsUseCase.js.map +1 -0
- package/admin/features/compareEntryRevisions/abstractions.d.ts +32 -0
- package/admin/features/compareEntryRevisions/abstractions.js +7 -0
- package/admin/features/compareEntryRevisions/abstractions.js.map +1 -0
- package/admin/features/compareEntryRevisions/feature.d.ts +1 -0
- package/admin/features/compareEntryRevisions/feature.js +15 -0
- package/admin/features/compareEntryRevisions/feature.js.map +1 -0
- package/admin/features/compareEntryRevisions/index.d.ts +2 -0
- package/admin/features/compareEntryRevisions/index.js +2 -0
- package/admin/features/feature.js +2 -0
- package/admin/features/feature.js.map +1 -1
- package/admin/features/generateEntryContent/GenerateEntryContentGateway.js +2 -0
- package/admin/features/generateEntryContent/GenerateEntryContentGateway.js.map +1 -1
- package/admin/features/generateEntryContent/abstractions.d.ts +1 -0
- package/admin/features/generateEntryContent/abstractions.js.map +1 -1
- package/admin/features/generatePageContent/GeneratePageContentGateway.js +2 -0
- package/admin/features/generatePageContent/GeneratePageContentGateway.js.map +1 -1
- package/admin/features/generatePageContent/abstractions.d.ts +1 -0
- package/admin/features/generatePageContent/abstractions.js.map +1 -1
- package/admin/presentation/AiPowerUpsSettings/AiPowerUpsSettingsPage.d.ts +2 -2
- package/admin/presentation/AiPromptFormFactory/AiPromptFormFactory.d.ts +20 -0
- package/admin/presentation/AiPromptFormFactory/AiPromptFormFactory.js +108 -0
- package/admin/presentation/AiPromptFormFactory/AiPromptFormFactory.js.map +1 -0
- package/admin/presentation/AiPromptFormFactory/abstractions.d.ts +14 -0
- package/admin/presentation/AiPromptFormFactory/abstractions.js +5 -0
- package/admin/presentation/AiPromptFormFactory/abstractions.js.map +1 -0
- package/admin/presentation/AiPromptFormFactory/feature.d.ts +1 -0
- package/admin/presentation/AiPromptFormFactory/feature.js +11 -0
- package/admin/presentation/AiPromptFormFactory/feature.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDialog.d.ts +9 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDialog.js +118 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDialog.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDrawer.d.ts +9 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDrawer.js +72 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDrawer.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsMenuItem.d.ts +4 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsMenuItem.js +21 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsMenuItem.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsOverlays.d.ts +2 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsOverlays.js +17 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsOverlays.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsPresenter.d.ts +25 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsPresenter.js +90 -0
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsPresenter.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/Extension.d.ts +2 -0
- package/admin/presentation/CmsCompareEntryRevisions/Extension.js +16 -0
- package/admin/presentation/CmsCompareEntryRevisions/Extension.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/abstractions.d.ts +29 -0
- package/admin/presentation/CmsCompareEntryRevisions/abstractions.js +5 -0
- package/admin/presentation/CmsCompareEntryRevisions/abstractions.js.map +1 -0
- package/admin/presentation/CmsCompareEntryRevisions/feature.d.ts +3 -0
- package/admin/presentation/CmsCompareEntryRevisions/feature.js +17 -0
- package/admin/presentation/CmsCompareEntryRevisions/feature.js.map +1 -0
- package/admin/presentation/CmsContentGeneration/CmsGenerateContentButton.d.ts +2 -2
- package/admin/presentation/CmsContentGeneration/CmsGenerateContentDialog.d.ts +2 -2
- package/admin/presentation/CmsContentGeneration/CmsGenerateContentPresenter.d.ts +4 -12
- package/admin/presentation/CmsContentGeneration/CmsGenerateContentPresenter.js +12 -94
- package/admin/presentation/CmsContentGeneration/CmsGenerateContentPresenter.js.map +1 -1
- package/admin/presentation/CmsContentGeneration/Extension.d.ts +1 -1
- package/admin/presentation/CmsContentGeneration/Extension.js +9 -5
- package/admin/presentation/CmsContentGeneration/Extension.js.map +1 -1
- package/admin/presentation/CmsEntryWizard/CmsEntryWizard.d.ts +4 -0
- package/admin/presentation/CmsEntryWizard/CmsEntryWizard.js +113 -0
- package/admin/presentation/CmsEntryWizard/CmsEntryWizard.js.map +1 -0
- package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.d.ts +4 -0
- package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js +19 -0
- package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js.map +1 -0
- package/admin/presentation/CmsEntryWizard/Extension.d.ts +2 -0
- package/admin/presentation/CmsEntryWizard/Extension.js +10 -0
- package/admin/presentation/CmsEntryWizard/Extension.js.map +1 -0
- package/admin/presentation/ProvidersSettings.js +1 -1
- package/admin/presentation/ProvidersSettings.js.map +1 -1
- package/admin/presentation/WbContentGeneration/GenerateContentDialog.d.ts +2 -2
- package/admin/presentation/WbContentGeneration/GenerateContentDialog.js +2 -2
- package/admin/presentation/WbContentGeneration/GenerateContentDialog.js.map +1 -1
- package/admin/presentation/WbContentGeneration/GenerateContentPresenter.d.ts +4 -12
- package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js +12 -94
- package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js.map +1 -1
- package/api/Extension.js +8 -0
- package/api/Extension.js.map +1 -1
- package/api/features/AiChatProvider/PowerUpsAiChatProvider.d.ts +24 -0
- package/api/features/AiChatProvider/PowerUpsAiChatProvider.js +31 -0
- package/api/features/AiChatProvider/PowerUpsAiChatProvider.js.map +1 -0
- package/api/features/AiChatProvider/feature.d.ts +9 -0
- package/api/features/AiChatProvider/feature.js +11 -0
- package/api/features/AiChatProvider/feature.js.map +1 -0
- package/api/features/AiChatProvider/index.d.ts +2 -0
- package/api/features/AiChatProvider/index.js +2 -0
- package/api/features/AiImageEnrichment/AiImageEnrichmentAfterCreateHandler.d.ts +1 -3
- package/api/features/AiImageEnrichment/AiImageEnrichmentAfterCreateHandler.js +2 -6
- package/api/features/AiImageEnrichment/AiImageEnrichmentAfterCreateHandler.js.map +1 -1
- package/api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.d.ts +36 -0
- package/api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.js +85 -0
- package/api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.js.map +1 -0
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.d.ts +10 -16
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js +27 -102
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js.map +1 -1
- package/api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.d.ts +18 -0
- package/api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.js +50 -0
- package/api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.js.map +1 -0
- package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.d.ts +19 -0
- package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js +49 -0
- package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js.map +1 -0
- package/api/features/AiImageEnrichment/abstractions.d.ts +60 -0
- package/api/features/AiImageEnrichment/abstractions.js +12 -0
- package/api/features/AiImageEnrichment/abstractions.js.map +1 -0
- package/api/features/AiImageEnrichment/buildEnrichmentAiRequest.d.ts +12 -0
- package/api/features/AiImageEnrichment/buildEnrichmentAiRequest.js +30 -0
- package/api/features/AiImageEnrichment/buildEnrichmentAiRequest.js.map +1 -0
- package/api/features/AiImageEnrichment/errors.d.ts +26 -0
- package/api/features/AiImageEnrichment/errors.js +28 -0
- package/api/features/AiImageEnrichment/errors.js.map +1 -0
- package/api/features/AiImageEnrichment/feature.js +9 -0
- package/api/features/AiImageEnrichment/feature.js.map +1 -1
- package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.d.ts +6 -0
- package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.js +9 -0
- package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.js.map +1 -0
- package/api/features/AiImageEnrichment/readEnrichmentPartial.d.ts +8 -0
- package/api/features/AiImageEnrichment/readEnrichmentPartial.js +16 -0
- package/api/features/AiImageEnrichment/readEnrichmentPartial.js.map +1 -0
- package/api/features/AiImageEnrichment/streamEvents.d.ts +36 -0
- package/api/features/AiImageEnrichment/streamEvents.js +0 -0
- package/api/features/AiPromptContext/AiPromptContextBuilder.d.ts +8 -1
- package/api/features/AiPromptContext/AiPromptContextBuilder.js +46 -2
- package/api/features/AiPromptContext/AiPromptContextBuilder.js.map +1 -1
- package/api/features/AiPromptContext/abstractions.d.ts +3 -0
- package/api/features/AiPromptContext/abstractions.js +9 -1
- package/api/features/AiPromptContext/abstractions.js.map +1 -1
- package/api/features/AiPromptContext/index.d.ts +1 -1
- package/api/features/AiPromptContext/index.js +1 -1
- package/api/features/CmsCompareEntryRevisions/CmsCompareEntryRevisionsUseCase.d.ts +22 -0
- package/api/features/CmsCompareEntryRevisions/CmsCompareEntryRevisionsUseCase.js +136 -0
- package/api/features/CmsCompareEntryRevisions/CmsCompareEntryRevisionsUseCase.js.map +1 -0
- package/api/features/CmsCompareEntryRevisions/abstractions.d.ts +18 -0
- package/api/features/CmsCompareEntryRevisions/abstractions.js +5 -0
- package/api/features/CmsCompareEntryRevisions/abstractions.js.map +1 -0
- package/api/features/CmsCompareEntryRevisions/feature.d.ts +4 -0
- package/api/features/CmsCompareEntryRevisions/feature.js +11 -0
- package/api/features/CmsCompareEntryRevisions/feature.js.map +1 -0
- package/api/features/CmsCompareEntryRevisions/index.d.ts +2 -0
- package/api/features/CmsCompareEntryRevisions/index.js +2 -0
- package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentTask.d.ts +1 -0
- package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentTask.js +20 -10
- package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentTask.js.map +1 -1
- package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentUseCase.d.ts +1 -1
- package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentUseCase.js +5 -5
- package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentUseCase.js.map +1 -1
- package/api/features/CmsGenerateEntryContent/abstractions.d.ts +10 -3
- package/api/features/CmsGenerateEntryContent/abstractions.js.map +1 -1
- package/api/features/GetSettings/index.d.ts +1 -0
- package/api/features/GetSettings/index.js +1 -0
- package/api/features/Providers/types.d.ts +2 -2
- package/api/features/WbGeneratePageContent/WbGeneratePageContentTask.d.ts +1 -0
- package/api/features/WbGeneratePageContent/WbGeneratePageContentTask.js +19 -9
- package/api/features/WbGeneratePageContent/WbGeneratePageContentTask.js.map +1 -1
- package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js +4 -3
- package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js.map +1 -1
- package/api/features/WbGeneratePageContent/abstractions.d.ts +1 -0
- package/api/features/WbGeneratePageContent/abstractions.js.map +1 -1
- package/api/features/WbTranslatePage/LexicalParser.d.ts +10 -0
- package/api/features/WbTranslatePage/LexicalParser.js +37 -0
- package/api/features/WbTranslatePage/LexicalParser.js.map +1 -0
- package/api/features/WbTranslatePage/WbTranslatePageDecorator.d.ts +27 -0
- package/api/features/WbTranslatePage/WbTranslatePageDecorator.js +134 -0
- package/api/features/WbTranslatePage/WbTranslatePageDecorator.js.map +1 -0
- package/api/features/WbTranslatePage/abstractions/LexicalParser.d.ts +7 -0
- package/api/features/WbTranslatePage/abstractions/LexicalParser.js +5 -0
- package/api/features/WbTranslatePage/abstractions/LexicalParser.js.map +1 -0
- package/api/features/WbTranslatePage/feature.d.ts +4 -0
- package/api/features/WbTranslatePage/feature.js +13 -0
- package/api/features/WbTranslatePage/feature.js.map +1 -0
- package/api/features/WbTranslatePage/index.d.ts +2 -0
- package/api/features/WbTranslatePage/index.js +2 -0
- package/api/graphql/BaseGraphQLSchema.js +11 -4
- package/api/graphql/BaseGraphQLSchema.js.map +1 -1
- package/api/graphql/CmsCompareEntryRevisionsSchema.d.ts +8 -0
- package/api/graphql/CmsCompareEntryRevisionsSchema.js +68 -0
- package/api/graphql/CmsCompareEntryRevisionsSchema.js.map +1 -0
- package/exports/admin/ai-powerups.d.ts +1 -0
- package/exports/admin/ai-powerups.js +1 -0
- package/exports/api/ai-powerups.d.ts +2 -0
- package/exports/api/ai-powerups.js +2 -0
- package/package.json +32 -25
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler, toSseFrame } from "@webiny/event-handler-core";
|
|
2
|
+
import { Ai } from "@webiny/api-core/features/ai/index.js";
|
|
3
|
+
import { PrepareImageEnrichmentUseCase } from "./abstractions.js";
|
|
4
|
+
import { buildEnrichmentAiRequest } from "./buildEnrichmentAiRequest.js";
|
|
5
|
+
import { readEnrichmentPartial } from "./readEnrichmentPartial.js";
|
|
6
|
+
import { imageEnrichmentErrorStatusCode } from "./imageEnrichmentErrorStatusCode.js";
|
|
7
|
+
class AiImageEnrichmentStreamRouteImpl {
|
|
8
|
+
constructor(prepare, ai){
|
|
9
|
+
this.prepare = prepare;
|
|
10
|
+
this.ai = ai;
|
|
11
|
+
}
|
|
12
|
+
async handle(request, response) {
|
|
13
|
+
const fileId = request.pathParameters.fileId;
|
|
14
|
+
if (!fileId) return response.status(400).json({
|
|
15
|
+
message: "Missing file ID."
|
|
16
|
+
});
|
|
17
|
+
const preparedResult = await this.prepare.execute(fileId);
|
|
18
|
+
if (preparedResult.isFail()) {
|
|
19
|
+
const error = preparedResult.error;
|
|
20
|
+
const statusCode = imageEnrichmentErrorStatusCode(error);
|
|
21
|
+
return response.status(statusCode).json({
|
|
22
|
+
message: error.message,
|
|
23
|
+
code: error.code
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const events = this.enrich(preparedResult.value);
|
|
27
|
+
return response.sse(events);
|
|
28
|
+
}
|
|
29
|
+
async *enrich(prepared) {
|
|
30
|
+
yield toSseFrame({
|
|
31
|
+
type: "start",
|
|
32
|
+
fileId: prepared.fileId,
|
|
33
|
+
model: prepared.model
|
|
34
|
+
});
|
|
35
|
+
let output = {
|
|
36
|
+
tags: [],
|
|
37
|
+
description: ""
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const request = buildEnrichmentAiRequest(prepared);
|
|
41
|
+
const stream = await this.ai.streamText(request);
|
|
42
|
+
for await (const partial of stream.partialOutputStream){
|
|
43
|
+
output = readEnrichmentPartial(partial);
|
|
44
|
+
yield toSseFrame({
|
|
45
|
+
type: "partial",
|
|
46
|
+
...output
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
yield toSseFrame({
|
|
51
|
+
type: "error",
|
|
52
|
+
message: `AI enrichment failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
yield toSseFrame({
|
|
57
|
+
type: "done",
|
|
58
|
+
fileId: prepared.fileId,
|
|
59
|
+
tags: output.tags,
|
|
60
|
+
description: output.description
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const AiImageEnrichmentStreamRoute = HttpRouteHandler.createImplementation({
|
|
65
|
+
implementation: AiImageEnrichmentStreamRouteImpl,
|
|
66
|
+
dependencies: [
|
|
67
|
+
PrepareImageEnrichmentUseCase,
|
|
68
|
+
Ai
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
class AiImageEnrichmentStreamRouteDefinitionImpl {
|
|
72
|
+
constructor(){
|
|
73
|
+
this.name = "ai-image-enrichment-stream";
|
|
74
|
+
this.method = "POST";
|
|
75
|
+
this.path = "/stream/fm/files/:fileId/enrich";
|
|
76
|
+
this.handler = AiImageEnrichmentStreamRoute;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const AiImageEnrichmentStreamRouteDefinition = HttpRouteDefinition.createImplementation({
|
|
80
|
+
implementation: AiImageEnrichmentStreamRouteDefinitionImpl,
|
|
81
|
+
dependencies: []
|
|
82
|
+
});
|
|
83
|
+
export { AiImageEnrichmentStreamRoute, AiImageEnrichmentStreamRouteDefinition };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=AiImageEnrichmentStreamRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.ts"],"sourcesContent":["import { HttpRouteDefinition, HttpRouteHandler, toSseFrame } from \"@webiny/event-handler-core\";\nimport type { IHttpRequest, IHttpResponseBuilder } from \"@webiny/event-handler-core\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { PrepareImageEnrichmentUseCase } from \"./abstractions.js\";\nimport type { IPreparedImageEnrichment } from \"./abstractions.js\";\nimport { buildEnrichmentAiRequest } from \"./buildEnrichmentAiRequest.js\";\nimport { readEnrichmentPartial } from \"./readEnrichmentPartial.js\";\nimport { imageEnrichmentErrorStatusCode } from \"./imageEnrichmentErrorStatusCode.js\";\n\n/**\n * Re-runs AI enrichment for a single file, streaming the model's output to the caller as\n * server-sent events. Read-only: the result is a proposal the caller can accept or discard.\n *\n * Preparation (file lookup, image read, provider resolution) happens BEFORE the response opens, so\n * anything knowable up front — missing file, wrong type, no provider — comes back as a real status\n * code. Only the AI call itself streams; once the first byte is out the status is committed to 200\n * and failures can only be reported as an `error` event.\n */\nclass AiImageEnrichmentStreamRouteImpl implements HttpRouteHandler.Interface {\n constructor(\n private prepare: PrepareImageEnrichmentUseCase.Interface,\n private ai: Ai.Interface\n ) {}\n\n async handle(\n request: IHttpRequest,\n response: IHttpResponseBuilder\n ): Promise<IHttpResponseBuilder> {\n const fileId = request.pathParameters.fileId;\n\n if (!fileId) {\n return response.status(400).json({ message: \"Missing file ID.\" });\n }\n\n const preparedResult = await this.prepare.execute(fileId);\n\n if (preparedResult.isFail()) {\n const error = preparedResult.error;\n\n const statusCode = imageEnrichmentErrorStatusCode(error);\n\n return response.status(statusCode).json({ message: error.message, code: error.code });\n }\n\n const events = this.enrich(preparedResult.value);\n\n return response.sse(events);\n }\n\n private async *enrich(prepared: IPreparedImageEnrichment): AsyncGenerator<string> {\n yield toSseFrame({ type: \"start\", fileId: prepared.fileId, model: prepared.model });\n\n let output = { tags: [] as string[], description: \"\" };\n\n try {\n const request = buildEnrichmentAiRequest(prepared);\n const stream = await this.ai.streamText(request);\n\n for await (const partial of stream.partialOutputStream) {\n output = readEnrichmentPartial(partial);\n yield toSseFrame({ type: \"partial\", ...output });\n }\n } catch (error) {\n yield toSseFrame({\n type: \"error\",\n message: `AI enrichment failed: ${\n error instanceof Error ? error.message : String(error)\n }`\n });\n return;\n }\n\n // Nothing is persisted here. The route PROPOSES; the user accepts in the UI, and the save\n // goes through the file's normal update path. The upload-time task still applies\n // automatically — there is nobody to ask at that point.\n yield toSseFrame({\n type: \"done\",\n fileId: prepared.fileId,\n tags: output.tags,\n description: output.description\n });\n }\n}\n\nexport const AiImageEnrichmentStreamRoute = HttpRouteHandler.createImplementation({\n implementation: AiImageEnrichmentStreamRouteImpl,\n dependencies: [PrepareImageEnrichmentUseCase, Ai]\n});\n\nclass AiImageEnrichmentStreamRouteDefinitionImpl implements HttpRouteDefinition.Interface {\n readonly name = \"ai-image-enrichment-stream\";\n readonly method = \"POST\";\n readonly path = \"/stream/fm/files/:fileId/enrich\";\n readonly handler = AiImageEnrichmentStreamRoute;\n}\n\n/** What the router matches on. Zero dependencies, so building it costs nothing. */\nexport const AiImageEnrichmentStreamRouteDefinition = HttpRouteDefinition.createImplementation({\n implementation: AiImageEnrichmentStreamRouteDefinitionImpl,\n dependencies: []\n});\n"],"names":["AiImageEnrichmentStreamRouteImpl","prepare","ai","request","response","fileId","preparedResult","error","statusCode","imageEnrichmentErrorStatusCode","events","prepared","toSseFrame","output","buildEnrichmentAiRequest","stream","partial","readEnrichmentPartial","Error","String","AiImageEnrichmentStreamRoute","HttpRouteHandler","PrepareImageEnrichmentUseCase","Ai","AiImageEnrichmentStreamRouteDefinitionImpl","AiImageEnrichmentStreamRouteDefinition","HttpRouteDefinition"],"mappings":";;;;;;AAkBA,MAAMA;IACF,YACYC,OAAgD,EAChDC,EAAgB,CAC1B;aAFUD,OAAO,GAAPA;aACAC,EAAE,GAAFA;IACT;IAEH,MAAM,OACFC,OAAqB,EACrBC,QAA8B,EACD;QAC7B,MAAMC,SAASF,QAAQ,cAAc,CAAC,MAAM;QAE5C,IAAI,CAACE,QACD,OAAOD,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;YAAE,SAAS;QAAmB;QAGnE,MAAME,iBAAiB,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAACD;QAElD,IAAIC,eAAe,MAAM,IAAI;YACzB,MAAMC,QAAQD,eAAe,KAAK;YAElC,MAAME,aAAaC,+BAA+BF;YAElD,OAAOH,SAAS,MAAM,CAACI,YAAY,IAAI,CAAC;gBAAE,SAASD,MAAM,OAAO;gBAAE,MAAMA,MAAM,IAAI;YAAC;QACvF;QAEA,MAAMG,SAAS,IAAI,CAAC,MAAM,CAACJ,eAAe,KAAK;QAE/C,OAAOF,SAAS,GAAG,CAACM;IACxB;IAEA,OAAe,OAAOC,QAAkC,EAA0B;QAC9E,MAAMC,WAAW;YAAE,MAAM;YAAS,QAAQD,SAAS,MAAM;YAAE,OAAOA,SAAS,KAAK;QAAC;QAEjF,IAAIE,SAAS;YAAE,MAAM,EAAE;YAAc,aAAa;QAAG;QAErD,IAAI;YACA,MAAMV,UAAUW,yBAAyBH;YACzC,MAAMI,SAAS,MAAM,IAAI,CAAC,EAAE,CAAC,UAAU,CAACZ;YAExC,WAAW,MAAMa,WAAWD,OAAO,mBAAmB,CAAE;gBACpDF,SAASI,sBAAsBD;gBAC/B,MAAMJ,WAAW;oBAAE,MAAM;oBAAW,GAAGC,MAAM;gBAAC;YAClD;QACJ,EAAE,OAAON,OAAO;YACZ,MAAMK,WAAW;gBACb,MAAM;gBACN,SAAS,CAAC,sBAAsB,EAC5BL,iBAAiBW,QAAQX,MAAM,OAAO,GAAGY,OAAOZ,QAClD;YACN;YACA;QACJ;QAKA,MAAMK,WAAW;YACb,MAAM;YACN,QAAQD,SAAS,MAAM;YACvB,MAAME,OAAO,IAAI;YACjB,aAAaA,OAAO,WAAW;QACnC;IACJ;AACJ;AAEO,MAAMO,+BAA+BC,iBAAiB,oBAAoB,CAAC;IAC9E,gBAAgBrB;IAChB,cAAc;QAACsB;QAA+BC;KAAG;AACrD;AAEA,MAAMC;;aACO,IAAI,GAAG;aACP,MAAM,GAAG;aACT,IAAI,GAAG;aACP,OAAO,GAAGJ;;AACvB;AAGO,MAAMK,yCAAyCC,oBAAoB,oBAAoB,CAAC;IAC3F,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -1,25 +1,19 @@
|
|
|
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 {
|
|
4
|
-
import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
|
|
5
|
-
import { GetFileContentsByIdUseCase } from "@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js";
|
|
6
|
-
import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
|
|
7
|
-
import { WebsocketsSendToIdentityUseCase } from "@webiny/api-websockets/features/SendToIdentity/abstractions.js";
|
|
8
|
-
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
9
|
-
import { GetSettingsUseCase } from "../../../api/features/GetSettings/index.js";
|
|
3
|
+
import { ApplyImageEnrichmentUseCase, PrepareImageEnrichmentUseCase } from "./abstractions.js";
|
|
10
4
|
export declare const AI_IMAGE_ENRICHMENT_TASK_ID = "fmAiImageEnrichment";
|
|
11
5
|
export interface IAiImageEnrichmentTaskInput {
|
|
12
6
|
fileId: string;
|
|
13
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Background enrichment, triggered after a file is created. Shares its preparation and persistence
|
|
10
|
+
* with the streaming HTTP route (`AiImageEnrichmentStreamRoute`); the only difference is that this
|
|
11
|
+
* one waits for the whole AI response, because a background task has no one to stream to.
|
|
12
|
+
*/
|
|
14
13
|
declare class AiImageEnrichmentTaskImpl implements TaskDefinition.Interface<IAiImageEnrichmentTaskInput> {
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
-
private updateFile;
|
|
14
|
+
private prepare;
|
|
15
|
+
private apply;
|
|
18
16
|
private ai;
|
|
19
|
-
private getSettings;
|
|
20
|
-
private encryption;
|
|
21
|
-
private identityContext;
|
|
22
|
-
private sendToIdentity;
|
|
23
17
|
id: string;
|
|
24
18
|
title: string;
|
|
25
19
|
description: string;
|
|
@@ -27,10 +21,10 @@ declare class AiImageEnrichmentTaskImpl implements TaskDefinition.Interface<IAiI
|
|
|
27
21
|
isPrivate: boolean;
|
|
28
22
|
databaseLogs: boolean;
|
|
29
23
|
readonly selfCleanup: ("onAbort" | "onSuccess")[];
|
|
30
|
-
constructor(
|
|
24
|
+
constructor(prepare: PrepareImageEnrichmentUseCase.Interface, apply: ApplyImageEnrichmentUseCase.Interface, ai: Ai.Interface);
|
|
31
25
|
run({ input, controller }: TaskDefinition.RunParams<IAiImageEnrichmentTaskInput>): Promise<TaskDefinition.Result<IAiImageEnrichmentTaskInput>>;
|
|
32
26
|
}
|
|
33
27
|
export declare const AiImageEnrichmentTask: typeof AiImageEnrichmentTaskImpl & {
|
|
34
|
-
__abstraction: import("@webiny/di").Abstraction<import("@webiny/api-core/features/task/TaskDefinition/abstractions").ITaskDefinition<import("@webiny/api-core/features/task/TaskDefinition/abstractions").ITaskInput, import("@webiny/api-core/features/task/TaskDefinition/abstractions").ITaskOutput>>;
|
|
28
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/api-core/features/task/TaskDefinition/abstractions.js").ITaskDefinition<import("@webiny/api-core/features/task/TaskDefinition/abstractions.js").ITaskInput, import("@webiny/api-core/features/task/TaskDefinition/abstractions.js").ITaskOutput>>;
|
|
35
29
|
};
|
|
36
30
|
export {};
|
|
@@ -1,32 +1,14 @@
|
|
|
1
|
-
import { Output } from "ai";
|
|
2
|
-
import { z } from "zod";
|
|
3
1
|
import { TaskDefinition } from "@webiny/api-core/features/task/TaskDefinition/index.js";
|
|
4
2
|
import { Ai } from "@webiny/api-core/features/ai/index.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
|
|
9
|
-
import { WebsocketsSendToIdentityUseCase } from "@webiny/api-websockets/features/SendToIdentity/abstractions.js";
|
|
10
|
-
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
11
|
-
import { GetSettingsUseCase } from "../GetSettings/index.js";
|
|
3
|
+
import { ApplyImageEnrichmentUseCase, PrepareImageEnrichmentUseCase } from "./abstractions.js";
|
|
4
|
+
import { buildEnrichmentAiRequest } from "./buildEnrichmentAiRequest.js";
|
|
5
|
+
import { EnrichmentNotAnImageError } from "./errors.js";
|
|
12
6
|
const AI_IMAGE_ENRICHMENT_TASK_ID = "fmAiImageEnrichment";
|
|
13
|
-
const AI_PROMPT = "Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.";
|
|
14
|
-
const aiOutputSchema = Output.object({
|
|
15
|
-
schema: z.object({
|
|
16
|
-
tags: z.array(z.string()),
|
|
17
|
-
description: z.string()
|
|
18
|
-
})
|
|
19
|
-
});
|
|
20
7
|
class AiImageEnrichmentTaskImpl {
|
|
21
|
-
constructor(
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.updateFile = updateFile;
|
|
8
|
+
constructor(prepare, apply, ai){
|
|
9
|
+
this.prepare = prepare;
|
|
10
|
+
this.apply = apply;
|
|
25
11
|
this.ai = ai;
|
|
26
|
-
this.getSettings = getSettings;
|
|
27
|
-
this.encryption = encryption;
|
|
28
|
-
this.identityContext = identityContext;
|
|
29
|
-
this.sendToIdentity = sendToIdentity;
|
|
30
12
|
this.id = AI_IMAGE_ENRICHMENT_TASK_ID;
|
|
31
13
|
this.title = "File Manager - AI Image Enrichment";
|
|
32
14
|
this.description = "Automatically enriches uploaded images with AI-generated tags and description.";
|
|
@@ -40,86 +22,34 @@ class AiImageEnrichmentTaskImpl {
|
|
|
40
22
|
}
|
|
41
23
|
async run({ input, controller }) {
|
|
42
24
|
if (controller.runtime.isAborted()) return controller.response.aborted();
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const contentsResult = await this.getFileContents.execute(input.fileId);
|
|
50
|
-
if (contentsResult.isFail()) return controller.response.error({
|
|
51
|
-
message: `Unable to read file contents: ${contentsResult.error.message}`
|
|
52
|
-
});
|
|
53
|
-
const imageBase64 = contentsResult.value.buffer.toString("base64");
|
|
54
|
-
const imageMediaType = contentsResult.value.contentType;
|
|
55
|
-
const aiSettingsResult = await this.getSettings.execute();
|
|
56
|
-
if (aiSettingsResult.isFail()) return controller.response.error({
|
|
57
|
-
message: "No AI provider configured. Add a provider in AI Power Ups settings."
|
|
58
|
-
});
|
|
59
|
-
const aiSettings = aiSettingsResult.value;
|
|
60
|
-
const firstProvider = aiSettings.providers.presets[0];
|
|
61
|
-
if (!firstProvider) return controller.response.done({
|
|
62
|
-
message: "No AI provider configured. Add a provider in AI Power Ups settings."
|
|
63
|
-
});
|
|
64
|
-
let tags = [];
|
|
65
|
-
let description = "";
|
|
66
|
-
try {
|
|
67
|
-
const aiResult = await this.ai.generateText({
|
|
68
|
-
model: firstProvider.model,
|
|
69
|
-
output: aiOutputSchema,
|
|
70
|
-
connection: {
|
|
71
|
-
sdkName: firstProvider.model.split("/")[0],
|
|
72
|
-
apiKey: await this.encryption.decrypt(firstProvider.apiKeyEncrypted)
|
|
73
|
-
},
|
|
74
|
-
messages: [
|
|
75
|
-
{
|
|
76
|
-
role: "user",
|
|
77
|
-
content: [
|
|
78
|
-
{
|
|
79
|
-
type: "file",
|
|
80
|
-
data: imageBase64,
|
|
81
|
-
mediaType: imageMediaType
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
type: "text",
|
|
85
|
-
text: AI_PROMPT
|
|
86
|
-
}
|
|
87
|
-
]
|
|
88
|
-
}
|
|
89
|
-
]
|
|
25
|
+
const preparedResult = await this.prepare.execute(input.fileId);
|
|
26
|
+
if (preparedResult.isFail()) {
|
|
27
|
+
const error = preparedResult.error;
|
|
28
|
+
if (error instanceof EnrichmentNotAnImageError) return controller.response.done(error.message);
|
|
29
|
+
return controller.response.error({
|
|
30
|
+
message: error.message
|
|
90
31
|
});
|
|
32
|
+
}
|
|
33
|
+
const prepared = preparedResult.value;
|
|
34
|
+
let tags;
|
|
35
|
+
let description;
|
|
36
|
+
try {
|
|
37
|
+
const request = buildEnrichmentAiRequest(prepared);
|
|
38
|
+
const aiResult = await this.ai.generateText(request);
|
|
91
39
|
tags = aiResult.output.tags;
|
|
92
40
|
description = aiResult.output.description;
|
|
93
41
|
} catch (error) {
|
|
94
|
-
console.log("error", error.message);
|
|
95
42
|
return controller.response.error({
|
|
96
43
|
message: `AI enrichment failed: ${error instanceof Error ? error.message : String(error)}`
|
|
97
44
|
});
|
|
98
45
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...tags
|
|
103
|
-
])
|
|
104
|
-
];
|
|
105
|
-
const updateResult = await this.updateFile.execute({
|
|
106
|
-
id: file.id,
|
|
107
|
-
tags: mergedTags,
|
|
46
|
+
const appliedResult = await this.apply.execute({
|
|
47
|
+
fileId: prepared.fileId,
|
|
48
|
+
tags,
|
|
108
49
|
description
|
|
109
50
|
});
|
|
110
|
-
if (
|
|
111
|
-
message:
|
|
112
|
-
});
|
|
113
|
-
const identity = this.identityContext.getIdentity();
|
|
114
|
-
await this.sendToIdentity.execute({
|
|
115
|
-
id: identity.id
|
|
116
|
-
}, {
|
|
117
|
-
action: "fm.file.enrichment",
|
|
118
|
-
data: {
|
|
119
|
-
id: file.id,
|
|
120
|
-
tags: mergedTags,
|
|
121
|
-
description
|
|
122
|
-
}
|
|
51
|
+
if (appliedResult.isFail()) return controller.response.error({
|
|
52
|
+
message: appliedResult.error.message
|
|
123
53
|
});
|
|
124
54
|
return controller.response.done("AI image enrichment completed successfully.");
|
|
125
55
|
}
|
|
@@ -127,14 +57,9 @@ class AiImageEnrichmentTaskImpl {
|
|
|
127
57
|
const AiImageEnrichmentTask = TaskDefinition.createImplementation({
|
|
128
58
|
implementation: AiImageEnrichmentTaskImpl,
|
|
129
59
|
dependencies: [
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
Ai,
|
|
134
|
-
GetSettingsUseCase,
|
|
135
|
-
Encryption,
|
|
136
|
-
IdentityContext,
|
|
137
|
-
WebsocketsSendToIdentityUseCase
|
|
60
|
+
PrepareImageEnrichmentUseCase,
|
|
61
|
+
ApplyImageEnrichmentUseCase,
|
|
62
|
+
Ai
|
|
138
63
|
]
|
|
139
64
|
});
|
|
140
65
|
export { AI_IMAGE_ENRICHMENT_TASK_ID, AiImageEnrichmentTask };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentTask.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentTask.ts"],"sourcesContent":["import { Output } from \"ai\";\nimport { z } from \"zod\";\nimport { TaskDefinition } from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\nimport { GetFileContentsByIdUseCase } from \"@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { WebsocketsSendToIdentityUseCase } from \"@webiny/api-websockets/features/SendToIdentity/abstractions.js\";\nimport { IdentityContext } from \"@webiny/api-core/exports/api/security.js\";\nimport { GetSettingsUseCase } from \"~/api/features/GetSettings/index.js\";\n\nexport const AI_IMAGE_ENRICHMENT_TASK_ID = \"fmAiImageEnrichment\";\n\nconst AI_PROMPT =\n \"Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.\";\n\nconst aiOutputSchema = Output.object({\n schema: z.object({\n tags: z.array(z.string()),\n description: z.string()\n })\n});\n\nexport interface IAiImageEnrichmentTaskInput {\n fileId: string;\n}\n\nclass AiImageEnrichmentTaskImpl implements TaskDefinition.Interface<IAiImageEnrichmentTaskInput> {\n id = AI_IMAGE_ENRICHMENT_TASK_ID;\n title = \"File Manager - AI Image Enrichment\";\n description = \"Automatically enriches uploaded images with AI-generated tags and description.\";\n maxIterations = 1;\n isPrivate = true;\n databaseLogs = false;\n\n public readonly selfCleanup = [\"onSuccess\" as const, \"onAbort\" as const];\n\n constructor(\n private getFile: GetFileUseCase.Interface,\n private getFileContents: GetFileContentsByIdUseCase.Interface,\n private updateFile: UpdateFileUseCase.Interface,\n private ai: Ai.Interface,\n private getSettings: GetSettingsUseCase.Interface,\n private encryption: Encryption.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 // 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(input.fileId);\n if (contentsResult.isFail()) {\n return controller.response.error({\n message: `Unable to read file contents: ${contentsResult.error.message}`\n });\n }\n const imageBase64 = contentsResult.value.buffer.toString(\"base64\");\n const imageMediaType = contentsResult.value.contentType;\n\n const aiSettingsResult = await this.getSettings.execute();\n\n if (aiSettingsResult.isFail()) {\n return controller.response.error({\n message: \"No AI provider configured. Add a provider in AI Power Ups settings.\"\n });\n }\n\n const aiSettings = aiSettingsResult.value;\n\n const firstProvider = aiSettings.providers.presets[0];\n\n if (!firstProvider) {\n return controller.response.done({\n message: \"No AI provider configured. Add a provider in AI Power Ups settings.\"\n });\n }\n\n let tags: string[] = [];\n let description = \"\";\n try {\n const aiResult = await this.ai.generateText({\n model: firstProvider.model,\n output: aiOutputSchema,\n connection: {\n sdkName: firstProvider.model.split(\"/\")[0],\n apiKey: await this.encryption.decrypt(firstProvider.apiKeyEncrypted)\n },\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"file\",\n data: imageBase64,\n mediaType: imageMediaType\n },\n {\n type: \"text\",\n text: AI_PROMPT\n }\n ]\n }\n ]\n });\n\n tags = aiResult.output.tags;\n description = aiResult.output.description;\n } catch (error) {\n console.log(\"error\", error.message);\n return controller.response.error({\n message: `AI enrichment failed: ${error instanceof Error ? error.message : String(error)}`\n });\n }\n\n const mergedTags = [...new Set([...file.tags, ...tags])];\n\n const updateResult = await this.updateFile.execute({\n id: file.id,\n tags: mergedTags,\n description\n });\n\n if (updateResult.isFail()) {\n return controller.response.error({\n message: `Failed to update file: ${updateResult.error.message}`\n });\n }\n\n 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 GetFileContentsByIdUseCase,\n UpdateFileUseCase,\n Ai,\n GetSettingsUseCase,\n Encryption,\n IdentityContext,\n WebsocketsSendToIdentityUseCase\n ]\n});\n"],"names":["AI_IMAGE_ENRICHMENT_TASK_ID","AI_PROMPT","aiOutputSchema","Output","z","AiImageEnrichmentTaskImpl","getFile","getFileContents","updateFile","ai","getSettings","encryption","identityContext","sendToIdentity","input","controller","fileResult","file","contentsResult","imageBase64","imageMediaType","aiSettingsResult","aiSettings","firstProvider","tags","description","aiResult","error","console","Error","String","mergedTags","Set","updateResult","identity","AiImageEnrichmentTask","TaskDefinition","GetFileUseCase","GetFileContentsByIdUseCase","UpdateFileUseCase","Ai","GetSettingsUseCase","Encryption","IdentityContext","WebsocketsSendToIdentityUseCase"],"mappings":";;;;;;;;;;;AAYO,MAAMA,8BAA8B;AAE3C,MAAMC,YACF;AAEJ,MAAMC,iBAAiBC,OAAO,MAAM,CAAC;IACjC,QAAQC,EAAE,MAAM,CAAC;QACb,MAAMA,EAAE,KAAK,CAACA,EAAE,MAAM;QACtB,aAAaA,EAAE,MAAM;IACzB;AACJ;AAMA,MAAMC;IAUF,YACYC,OAAiC,EACjCC,eAAqD,EACrDC,UAAuC,EACvCC,EAAgB,EAChBC,WAAyC,EACzCC,UAAgC,EAChCC,eAA0C,EAC1CC,cAAyD,CACnE;aARUP,OAAO,GAAPA;aACAC,eAAe,GAAfA;aACAC,UAAU,GAAVA;aACAC,EAAE,GAAFA;aACAC,WAAW,GAAXA;aACAC,UAAU,GAAVA;aACAC,eAAe,GAAfA;aACAC,cAAc,GAAdA;aAjBZ,EAAE,GAAGb;aACL,KAAK,GAAG;aACR,WAAW,GAAG;aACd,aAAa,GAAG;aAChB,SAAS,GAAG;aACZ,YAAY,GAAG;aAEC,WAAW,GAAG;YAAC;YAAsB;SAAmB;IAWrE;IAEH,MAAM,IAAI,EACNc,KAAK,EACLC,UAAU,EAC0C,EAEtD;QACE,IAAIA,WAAW,OAAO,CAAC,SAAS,IAC5B,OAAOA,WAAW,QAAQ,CAAC,OAAO;QAGtC,MAAMC,aAAa,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAACF,MAAM,MAAM;QAC1D,IAAIE,WAAW,MAAM,IACjB,OAAOD,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS,CAAC,gBAAgB,EAAED,MAAM,MAAM,EAAE;QAC9C;QAGJ,MAAMG,OAAOD,WAAW,KAAK;QAE7B,IAAI,CAACC,KAAK,IAAI,CAAC,UAAU,CAAC,WACtB,OAAOF,WAAW,QAAQ,CAAC,IAAI,CAAC;QAOpC,MAAMG,iBAAiB,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAACJ,MAAM,MAAM;QACtE,IAAII,eAAe,MAAM,IACrB,OAAOH,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS,CAAC,8BAA8B,EAAEG,eAAe,KAAK,CAAC,OAAO,EAAE;QAC5E;QAEJ,MAAMC,cAAcD,eAAe,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QACzD,MAAME,iBAAiBF,eAAe,KAAK,CAAC,WAAW;QAEvD,MAAMG,mBAAmB,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO;QAEvD,IAAIA,iBAAiB,MAAM,IACvB,OAAON,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS;QACb;QAGJ,MAAMO,aAAaD,iBAAiB,KAAK;QAEzC,MAAME,gBAAgBD,WAAW,SAAS,CAAC,OAAO,CAAC,EAAE;QAErD,IAAI,CAACC,eACD,OAAOR,WAAW,QAAQ,CAAC,IAAI,CAAC;YAC5B,SAAS;QACb;QAGJ,IAAIS,OAAiB,EAAE;QACvB,IAAIC,cAAc;QAClB,IAAI;YACA,MAAMC,WAAW,MAAM,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC;gBACxC,OAAOH,cAAc,KAAK;gBAC1B,QAAQrB;gBACR,YAAY;oBACR,SAASqB,cAAc,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;oBAC1C,QAAQ,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAACA,cAAc,eAAe;gBACvE;gBACA,UAAU;oBACN;wBACI,MAAM;wBACN,SAAS;4BACL;gCACI,MAAM;gCACN,MAAMJ;gCACN,WAAWC;4BACf;4BACA;gCACI,MAAM;gCACN,MAAMnB;4BACV;yBACH;oBACL;iBACH;YACL;YAEAuB,OAAOE,SAAS,MAAM,CAAC,IAAI;YAC3BD,cAAcC,SAAS,MAAM,CAAC,WAAW;QAC7C,EAAE,OAAOC,OAAO;YACZC,QAAQ,GAAG,CAAC,SAASD,MAAM,OAAO;YAClC,OAAOZ,WAAW,QAAQ,CAAC,KAAK,CAAC;gBAC7B,SAAS,CAAC,sBAAsB,EAAEY,iBAAiBE,QAAQF,MAAM,OAAO,GAAGG,OAAOH,QAAQ;YAC9F;QACJ;QAEA,MAAMI,aAAa;eAAI,IAAIC,IAAI;mBAAIf,KAAK,IAAI;mBAAKO;aAAK;SAAE;QAExD,MAAMS,eAAe,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAC/C,IAAIhB,KAAK,EAAE;YACX,MAAMc;YACNN;QACJ;QAEA,IAAIQ,aAAa,MAAM,IACnB,OAAOlB,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS,CAAC,uBAAuB,EAAEkB,aAAa,KAAK,CAAC,OAAO,EAAE;QACnE;QAGJ,MAAMC,WAAW,IAAI,CAAC,eAAe,CAAC,WAAW;QACjD,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAC7B;YAAE,IAAIA,SAAS,EAAE;QAAC,GAClB;YACI,QAAQ;YACR,MAAM;gBACF,IAAIjB,KAAK,EAAE;gBACX,MAAMc;gBACNN;YACJ;QACJ;QAGJ,OAAOV,WAAW,QAAQ,CAAC,IAAI,CAAC;IACpC;AACJ;AAEO,MAAMoB,wBAAwBC,eAAe,oBAAoB,CAAC;IACrE,gBAAgB/B;IAChB,cAAc;QACVgC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;KACH;AACL"}
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentTask.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentTask.ts"],"sourcesContent":["import { TaskDefinition } 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 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 prepare: PrepareImageEnrichmentUseCase.Interface,\n private apply: ApplyImageEnrichmentUseCase.Interface,\n private ai: Ai.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 preparedResult = await this.prepare.execute(input.fileId);\n if (preparedResult.isFail()) {\n const error = preparedResult.error;\n // A non-image isn't a failure — nothing to enrich, so the task is simply done.\n if (error instanceof EnrichmentNotAnImageError) {\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\nexport const AiImageEnrichmentTask = TaskDefinition.createImplementation({\n implementation: AiImageEnrichmentTaskImpl,\n dependencies: [PrepareImageEnrichmentUseCase, ApplyImageEnrichmentUseCase, Ai]\n});\n"],"names":["AI_IMAGE_ENRICHMENT_TASK_ID","AiImageEnrichmentTaskImpl","prepare","apply","ai","input","controller","preparedResult","error","EnrichmentNotAnImageError","prepared","tags","description","request","buildEnrichmentAiRequest","aiResult","Error","String","appliedResult","AiImageEnrichmentTask","TaskDefinition","PrepareImageEnrichmentUseCase","ApplyImageEnrichmentUseCase","Ai"],"mappings":";;;;;AAMO,MAAMA,8BAA8B;AAW3C,MAAMC;IAUF,YACYC,OAAgD,EAChDC,KAA4C,EAC5CC,EAAgB,CAC1B;aAHUF,OAAO,GAAPA;aACAC,KAAK,GAALA;aACAC,EAAE,GAAFA;aAZZ,EAAE,GAAGJ;aACL,KAAK,GAAG;aACR,WAAW,GAAG;aACd,aAAa,GAAG;aAChB,SAAS,GAAG;aACZ,YAAY,GAAG;aAEC,WAAW,GAAG;YAAC;YAAsB;SAAmB;IAMrE;IAEH,MAAM,IAAI,EACNK,KAAK,EACLC,UAAU,EAC0C,EAEtD;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;YAElC,IAAIC,iBAAiBC,2BACjB,OAAOH,WAAW,QAAQ,CAAC,IAAI,CAACE,MAAM,OAAO;YAEjD,OAAOF,WAAW,QAAQ,CAAC,KAAK,CAAC;gBAAE,SAASE,MAAM,OAAO;YAAC;QAC9D;QAEA,MAAME,WAAWH,eAAe,KAAK;QAErC,IAAII;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,OAAOP,OAAO;YACZ,OAAOF,WAAW,QAAQ,CAAC,KAAK,CAAC;gBAC7B,SAAS,CAAC,sBAAsB,EAAEE,iBAAiBQ,QAAQR,MAAM,OAAO,GAAGS,OAAOT,QAAQ;YAC9F;QACJ;QAEA,MAAMU,gBAAgB,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAC3C,QAAQR,SAAS,MAAM;YACvBC;YACAC;QACJ;QAEA,IAAIM,cAAc,MAAM,IACpB,OAAOZ,WAAW,QAAQ,CAAC,KAAK,CAAC;YAAE,SAASY,cAAc,KAAK,CAAC,OAAO;QAAC;QAG5E,OAAOZ,WAAW,QAAQ,CAAC,IAAI,CAAC;IACpC;AACJ;AAEO,MAAMa,wBAAwBC,eAAe,oBAAoB,CAAC;IACrE,gBAAgBnB;IAChB,cAAc;QAACoB;QAA+BC;QAA6BC;KAAG;AAClF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Result } from "@webiny/feature/api";
|
|
2
|
+
import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
|
|
3
|
+
import { WebsocketsSendToIdentityUseCase } from "@webiny/api-websockets/features/SendToIdentity/abstractions.js";
|
|
4
|
+
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
5
|
+
import { ApplyImageEnrichmentUseCase as UseCaseAbstraction, type IApplyImageEnrichmentParams, type IAppliedImageEnrichment } from "./abstractions.js";
|
|
6
|
+
import type { ImageEnrichmentError } from "./errors.js";
|
|
7
|
+
export declare const FILE_ENRICHMENT_WEBSOCKET_ACTION = "fm.file.enrichment";
|
|
8
|
+
declare class ApplyImageEnrichmentUseCaseImpl implements UseCaseAbstraction.Interface {
|
|
9
|
+
private updateFile;
|
|
10
|
+
private identityContext;
|
|
11
|
+
private sendToIdentity;
|
|
12
|
+
constructor(updateFile: UpdateFileUseCase.Interface, identityContext: IdentityContext.Interface, sendToIdentity: WebsocketsSendToIdentityUseCase.Interface);
|
|
13
|
+
execute(params: IApplyImageEnrichmentParams): Promise<Result<IAppliedImageEnrichment, ImageEnrichmentError>>;
|
|
14
|
+
}
|
|
15
|
+
export declare const ApplyImageEnrichmentUseCase: typeof ApplyImageEnrichmentUseCaseImpl & {
|
|
16
|
+
__abstraction: import("@webiny/di").Abstraction<import("./abstractions.js").IApplyImageEnrichmentUseCase>;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Result } from "@webiny/feature/api";
|
|
2
|
+
import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
|
|
3
|
+
import { WebsocketsSendToIdentityUseCase } from "@webiny/api-websockets/features/SendToIdentity/abstractions.js";
|
|
4
|
+
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
5
|
+
import { ApplyImageEnrichmentUseCase } from "./abstractions.js";
|
|
6
|
+
import { EnrichmentPersistError } from "./errors.js";
|
|
7
|
+
const FILE_ENRICHMENT_WEBSOCKET_ACTION = "fm.file.enrichment";
|
|
8
|
+
class ApplyImageEnrichmentUseCaseImpl {
|
|
9
|
+
constructor(updateFile, identityContext, sendToIdentity){
|
|
10
|
+
this.updateFile = updateFile;
|
|
11
|
+
this.identityContext = identityContext;
|
|
12
|
+
this.sendToIdentity = sendToIdentity;
|
|
13
|
+
}
|
|
14
|
+
async execute(params) {
|
|
15
|
+
const { tags, description, fileId } = params;
|
|
16
|
+
const updateResult = await this.updateFile.execute({
|
|
17
|
+
id: fileId,
|
|
18
|
+
tags,
|
|
19
|
+
description
|
|
20
|
+
});
|
|
21
|
+
if (updateResult.isFail()) return Result.fail(new EnrichmentPersistError(updateResult.error.message));
|
|
22
|
+
const identity = this.identityContext.getIdentity();
|
|
23
|
+
await this.sendToIdentity.execute({
|
|
24
|
+
id: identity.id
|
|
25
|
+
}, {
|
|
26
|
+
action: FILE_ENRICHMENT_WEBSOCKET_ACTION,
|
|
27
|
+
data: {
|
|
28
|
+
id: fileId,
|
|
29
|
+
tags,
|
|
30
|
+
description
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return Result.ok({
|
|
34
|
+
fileId,
|
|
35
|
+
tags,
|
|
36
|
+
description
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const ApplyImageEnrichmentUseCase_ApplyImageEnrichmentUseCase = ApplyImageEnrichmentUseCase.createImplementation({
|
|
41
|
+
implementation: ApplyImageEnrichmentUseCaseImpl,
|
|
42
|
+
dependencies: [
|
|
43
|
+
UpdateFileUseCase,
|
|
44
|
+
IdentityContext,
|
|
45
|
+
WebsocketsSendToIdentityUseCase
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
export { ApplyImageEnrichmentUseCase_ApplyImageEnrichmentUseCase as ApplyImageEnrichmentUseCase, FILE_ENRICHMENT_WEBSOCKET_ACTION };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=ApplyImageEnrichmentUseCase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.js","sources":["../../../../src/api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.ts"],"sourcesContent":["import { Result } from \"@webiny/feature/api\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { WebsocketsSendToIdentityUseCase } from \"@webiny/api-websockets/features/SendToIdentity/abstractions.js\";\nimport { IdentityContext } from \"@webiny/api-core/exports/api/security.js\";\nimport {\n ApplyImageEnrichmentUseCase as UseCaseAbstraction,\n type IApplyImageEnrichmentParams,\n type IAppliedImageEnrichment\n} from \"./abstractions.js\";\nimport { EnrichmentPersistError } from \"./errors.js\";\nimport type { ImageEnrichmentError } from \"./errors.js\";\n\nexport const FILE_ENRICHMENT_WEBSOCKET_ACTION = \"fm.file.enrichment\";\n\nclass ApplyImageEnrichmentUseCaseImpl implements UseCaseAbstraction.Interface {\n constructor(\n private updateFile: UpdateFileUseCase.Interface,\n private identityContext: IdentityContext.Interface,\n private sendToIdentity: WebsocketsSendToIdentityUseCase.Interface\n ) {}\n\n async execute(\n params: IApplyImageEnrichmentParams\n ): Promise<Result<IAppliedImageEnrichment, ImageEnrichmentError>> {\n // Enrichment REPLACES both fields. Tags used to be merged into the file's existing ones while\n // the description was overwritten, which is an odd split: re-running would accumulate tags\n // forever, so a wrong tag from an earlier run could never be dropped by re-running.\n const { tags, description, fileId } = params;\n\n const updateResult = await this.updateFile.execute({\n id: fileId,\n tags,\n description\n });\n\n if (updateResult.isFail()) {\n return Result.fail(new EnrichmentPersistError(updateResult.error.message));\n }\n\n // Push to the identity's open sockets even when the caller is streaming: the browser tab that\n // triggered this isn't necessarily the only one showing the file, and the client-side list\n // cache updates off this message (see AiImageEnrichmentEventHandler in @webiny/app-file-manager).\n const identity = this.identityContext.getIdentity();\n await this.sendToIdentity.execute(\n { id: identity.id },\n {\n action: FILE_ENRICHMENT_WEBSOCKET_ACTION,\n data: { id: fileId, tags, description }\n }\n );\n\n return Result.ok({ fileId, tags, description });\n }\n}\n\nexport const ApplyImageEnrichmentUseCase = UseCaseAbstraction.createImplementation({\n implementation: ApplyImageEnrichmentUseCaseImpl,\n dependencies: [UpdateFileUseCase, IdentityContext, WebsocketsSendToIdentityUseCase]\n});\n"],"names":["FILE_ENRICHMENT_WEBSOCKET_ACTION","ApplyImageEnrichmentUseCaseImpl","updateFile","identityContext","sendToIdentity","params","tags","description","fileId","updateResult","Result","EnrichmentPersistError","identity","ApplyImageEnrichmentUseCase","UseCaseAbstraction","UpdateFileUseCase","IdentityContext","WebsocketsSendToIdentityUseCase"],"mappings":";;;;;;AAYO,MAAMA,mCAAmC;AAEhD,MAAMC;IACF,YACYC,UAAuC,EACvCC,eAA0C,EAC1CC,cAAyD,CACnE;aAHUF,UAAU,GAAVA;aACAC,eAAe,GAAfA;aACAC,cAAc,GAAdA;IACT;IAEH,MAAM,QACFC,MAAmC,EAC2B;QAI9D,MAAM,EAAEC,IAAI,EAAEC,WAAW,EAAEC,MAAM,EAAE,GAAGH;QAEtC,MAAMI,eAAe,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAC/C,IAAID;YACJF;YACAC;QACJ;QAEA,IAAIE,aAAa,MAAM,IACnB,OAAOC,OAAO,IAAI,CAAC,IAAIC,uBAAuBF,aAAa,KAAK,CAAC,OAAO;QAM5E,MAAMG,WAAW,IAAI,CAAC,eAAe,CAAC,WAAW;QACjD,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAC7B;YAAE,IAAIA,SAAS,EAAE;QAAC,GAClB;YACI,QAAQZ;YACR,MAAM;gBAAE,IAAIQ;gBAAQF;gBAAMC;YAAY;QAC1C;QAGJ,OAAOG,OAAO,EAAE,CAAC;YAAEF;YAAQF;YAAMC;QAAY;IACjD;AACJ;AAEO,MAAMM,0DAA8BC,4BAAAA,oBAAuC,CAAC;IAC/E,gBAAgBb;IAChB,cAAc;QAACc;QAAmBC;QAAiBC;KAAgC;AACvF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Result } from "@webiny/feature/api";
|
|
2
|
+
import { Encryption } from "@webiny/api-core/features/encryption/index.js";
|
|
3
|
+
import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
|
|
4
|
+
import { GetFileContentsByIdUseCase } from "@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js";
|
|
5
|
+
import { PrepareImageEnrichmentUseCase as UseCaseAbstraction, type IPreparedImageEnrichment } from "./abstractions.js";
|
|
6
|
+
import { GetSettingsUseCase } from "../../../api/features/GetSettings/index.js";
|
|
7
|
+
import type { ImageEnrichmentError } from "./errors.js";
|
|
8
|
+
declare class PrepareImageEnrichmentUseCaseImpl implements UseCaseAbstraction.Interface {
|
|
9
|
+
private getFile;
|
|
10
|
+
private getFileContents;
|
|
11
|
+
private getSettings;
|
|
12
|
+
private encryption;
|
|
13
|
+
constructor(getFile: GetFileUseCase.Interface, getFileContents: GetFileContentsByIdUseCase.Interface, getSettings: GetSettingsUseCase.Interface, encryption: Encryption.Interface);
|
|
14
|
+
execute(fileId: string): Promise<Result<IPreparedImageEnrichment, ImageEnrichmentError>>;
|
|
15
|
+
}
|
|
16
|
+
export declare const PrepareImageEnrichmentUseCase: typeof PrepareImageEnrichmentUseCaseImpl & {
|
|
17
|
+
__abstraction: import("@webiny/di").Abstraction<import("./abstractions.js").IPrepareImageEnrichmentUseCase>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Result } from "@webiny/feature/api";
|
|
2
|
+
import { Encryption } from "@webiny/api-core/features/encryption/index.js";
|
|
3
|
+
import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
|
|
4
|
+
import { GetFileContentsByIdUseCase } from "@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js";
|
|
5
|
+
import { PrepareImageEnrichmentUseCase } from "./abstractions.js";
|
|
6
|
+
import { GetSettingsUseCase } from "../GetSettings/index.js";
|
|
7
|
+
import { EnrichmentFileContentsError, EnrichmentFileNotFoundError, EnrichmentNoProviderError, EnrichmentNotAnImageError } from "./errors.js";
|
|
8
|
+
class PrepareImageEnrichmentUseCaseImpl {
|
|
9
|
+
constructor(getFile, getFileContents, getSettings, encryption){
|
|
10
|
+
this.getFile = getFile;
|
|
11
|
+
this.getFileContents = getFileContents;
|
|
12
|
+
this.getSettings = getSettings;
|
|
13
|
+
this.encryption = encryption;
|
|
14
|
+
}
|
|
15
|
+
async execute(fileId) {
|
|
16
|
+
const fileResult = await this.getFile.execute(fileId);
|
|
17
|
+
if (fileResult.isFail()) return Result.fail(new EnrichmentFileNotFoundError(fileId));
|
|
18
|
+
const file = fileResult.value;
|
|
19
|
+
if (!file.type.startsWith("image/")) return Result.fail(new EnrichmentNotAnImageError(file.type));
|
|
20
|
+
const contentsResult = await this.getFileContents.execute(fileId);
|
|
21
|
+
if (contentsResult.isFail()) return Result.fail(new EnrichmentFileContentsError(contentsResult.error.message));
|
|
22
|
+
const aiSettingsResult = await this.getSettings.execute();
|
|
23
|
+
if (aiSettingsResult.isFail()) return Result.fail(new EnrichmentNoProviderError());
|
|
24
|
+
const firstProvider = aiSettingsResult.value.providers.presets[0];
|
|
25
|
+
if (!firstProvider) return Result.fail(new EnrichmentNoProviderError());
|
|
26
|
+
return Result.ok({
|
|
27
|
+
fileId: file.id,
|
|
28
|
+
imageBase64: contentsResult.value.buffer.toString("base64"),
|
|
29
|
+
imageMediaType: contentsResult.value.contentType,
|
|
30
|
+
model: firstProvider.model,
|
|
31
|
+
connection: {
|
|
32
|
+
sdkName: firstProvider.model.split("/")[0],
|
|
33
|
+
apiKey: await this.encryption.decrypt(firstProvider.apiKeyEncrypted)
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const PrepareImageEnrichmentUseCase_PrepareImageEnrichmentUseCase = PrepareImageEnrichmentUseCase.createImplementation({
|
|
39
|
+
implementation: PrepareImageEnrichmentUseCaseImpl,
|
|
40
|
+
dependencies: [
|
|
41
|
+
GetFileUseCase,
|
|
42
|
+
GetFileContentsByIdUseCase,
|
|
43
|
+
GetSettingsUseCase,
|
|
44
|
+
Encryption
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
export { PrepareImageEnrichmentUseCase_PrepareImageEnrichmentUseCase as PrepareImageEnrichmentUseCase };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=PrepareImageEnrichmentUseCase.js.map
|
|
@@ -0,0 +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 { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\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 { GetSettingsUseCase } from \"~/api/features/GetSettings/index.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 getSettings: GetSettingsUseCase.Interface,\n private encryption: Encryption.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 const aiSettingsResult = await this.getSettings.execute();\n if (aiSettingsResult.isFail()) {\n return Result.fail(new EnrichmentNoProviderError());\n }\n\n const firstProvider = aiSettingsResult.value.providers.presets[0];\n if (!firstProvider) {\n return Result.fail(new EnrichmentNoProviderError());\n }\n\n return Result.ok({\n fileId: file.id,\n imageBase64: contentsResult.value.buffer.toString(\"base64\"),\n imageMediaType: contentsResult.value.contentType,\n model: firstProvider.model,\n connection: {\n sdkName: firstProvider.model.split(\"/\")[0],\n apiKey: await this.encryption.decrypt(firstProvider.apiKeyEncrypted)\n }\n });\n }\n}\n\nexport const PrepareImageEnrichmentUseCase = UseCaseAbstraction.createImplementation({\n implementation: PrepareImageEnrichmentUseCaseImpl,\n dependencies: [GetFileUseCase, GetFileContentsByIdUseCase, GetSettingsUseCase, Encryption]\n});\n"],"names":["PrepareImageEnrichmentUseCaseImpl","getFile","getFileContents","getSettings","encryption","fileId","fileResult","Result","EnrichmentFileNotFoundError","file","EnrichmentNotAnImageError","contentsResult","EnrichmentFileContentsError","aiSettingsResult","EnrichmentNoProviderError","firstProvider","PrepareImageEnrichmentUseCase","UseCaseAbstraction","GetFileUseCase","GetFileContentsByIdUseCase","GetSettingsUseCase","Encryption"],"mappings":";;;;;;;AAiBA,MAAMA;IACF,YACYC,OAAiC,EACjCC,eAAqD,EACrDC,WAAyC,EACzCC,UAAgC,CAC1C;aAJUH,OAAO,GAAPA;aACAC,eAAe,GAAfA;aACAC,WAAW,GAAXA;aACAC,UAAU,GAAVA;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;QAO9D,MAAME,iBAAiB,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAACN;QAC1D,IAAIM,eAAe,MAAM,IACrB,OAAOJ,OAAO,IAAI,CAAC,IAAIK,4BAA4BD,eAAe,KAAK,CAAC,OAAO;QAGnF,MAAME,mBAAmB,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO;QACvD,IAAIA,iBAAiB,MAAM,IACvB,OAAON,OAAO,IAAI,CAAC,IAAIO;QAG3B,MAAMC,gBAAgBF,iBAAiB,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;QACjE,IAAI,CAACE,eACD,OAAOR,OAAO,IAAI,CAAC,IAAIO;QAG3B,OAAOP,OAAO,EAAE,CAAC;YACb,QAAQE,KAAK,EAAE;YACf,aAAaE,eAAe,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;YAClD,gBAAgBA,eAAe,KAAK,CAAC,WAAW;YAChD,OAAOI,cAAc,KAAK;YAC1B,YAAY;gBACR,SAASA,cAAc,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;gBAC1C,QAAQ,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAACA,cAAc,eAAe;YACvE;QACJ;IACJ;AACJ;AAEO,MAAMC,8DAAgCC,8BAAAA,oBAAuC,CAAC;IACjF,gBAAgBjB;IAChB,cAAc;QAACkB;QAAgBC;QAA4BC;QAAoBC;KAAW;AAC9F"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Result } from "@webiny/feature/api";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { ImageEnrichmentError } from "./errors.js";
|
|
4
|
+
export declare const AI_ENRICHMENT_PROMPT = "Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.";
|
|
5
|
+
/**
|
|
6
|
+
* The shape both entry points ask the model for. Exported as the bare zod schema rather than a
|
|
7
|
+
* ready-made `Output.object(...)`: the AI SDK's `Output` type can't be named in emitted declarations
|
|
8
|
+
* (TS4023), so each call site wraps this locally instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare const aiEnrichmentSchema: z.ZodObject<{
|
|
11
|
+
tags: z.ZodArray<z.ZodString>;
|
|
12
|
+
description: z.ZodString;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
/** The enrichment result: what the model produces and what gets persisted. */
|
|
15
|
+
export interface IEnrichmentOutput {
|
|
16
|
+
tags: string[];
|
|
17
|
+
description: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Everything needed to run the AI call, gathered up front: the image bytes, the resolved provider,
|
|
21
|
+
* and the file's current tags (needed to merge rather than overwrite).
|
|
22
|
+
*
|
|
23
|
+
* Deliberately split from the AI call itself so both entry points share the same preparation and the
|
|
24
|
+
* same failure modes — and so the streaming route can answer with a real HTTP status code for
|
|
25
|
+
* anything knowable before the stream opens, instead of burying it in a 200 response.
|
|
26
|
+
*/
|
|
27
|
+
export interface IPreparedImageEnrichment {
|
|
28
|
+
fileId: string;
|
|
29
|
+
imageBase64: string;
|
|
30
|
+
imageMediaType: string;
|
|
31
|
+
model: string;
|
|
32
|
+
connection: {
|
|
33
|
+
sdkName: string;
|
|
34
|
+
apiKey: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface IPrepareImageEnrichmentUseCase {
|
|
38
|
+
execute(fileId: string): Promise<Result<IPreparedImageEnrichment, ImageEnrichmentError>>;
|
|
39
|
+
}
|
|
40
|
+
export declare const PrepareImageEnrichmentUseCase: import("@webiny/di").Abstraction<IPrepareImageEnrichmentUseCase>;
|
|
41
|
+
export declare namespace PrepareImageEnrichmentUseCase {
|
|
42
|
+
type Interface = IPrepareImageEnrichmentUseCase;
|
|
43
|
+
}
|
|
44
|
+
export interface IApplyImageEnrichmentParams {
|
|
45
|
+
fileId: string;
|
|
46
|
+
tags: string[];
|
|
47
|
+
description: string;
|
|
48
|
+
}
|
|
49
|
+
export interface IAppliedImageEnrichment {
|
|
50
|
+
fileId: string;
|
|
51
|
+
tags: string[];
|
|
52
|
+
description: string;
|
|
53
|
+
}
|
|
54
|
+
export interface IApplyImageEnrichmentUseCase {
|
|
55
|
+
execute(params: IApplyImageEnrichmentParams): Promise<Result<IAppliedImageEnrichment, ImageEnrichmentError>>;
|
|
56
|
+
}
|
|
57
|
+
export declare const ApplyImageEnrichmentUseCase: import("@webiny/di").Abstraction<IApplyImageEnrichmentUseCase>;
|
|
58
|
+
export declare namespace ApplyImageEnrichmentUseCase {
|
|
59
|
+
type Interface = IApplyImageEnrichmentUseCase;
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createAbstraction } from "@webiny/feature/api";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const AI_ENRICHMENT_PROMPT = "Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.";
|
|
4
|
+
const aiEnrichmentSchema = z.object({
|
|
5
|
+
tags: z.array(z.string()),
|
|
6
|
+
description: z.string()
|
|
7
|
+
});
|
|
8
|
+
const PrepareImageEnrichmentUseCase = createAbstraction("PrepareImageEnrichmentUseCase");
|
|
9
|
+
const ApplyImageEnrichmentUseCase = createAbstraction("ApplyImageEnrichmentUseCase");
|
|
10
|
+
export { AI_ENRICHMENT_PROMPT, ApplyImageEnrichmentUseCase, PrepareImageEnrichmentUseCase, aiEnrichmentSchema };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=abstractions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/abstractions.js","sources":["../../../../src/api/features/AiImageEnrichment/abstractions.ts"],"sourcesContent":["import { createAbstraction, Result } from \"@webiny/feature/api\";\nimport { z } from \"zod\";\nimport type { ImageEnrichmentError } from \"./errors.js\";\n\nexport const AI_ENRICHMENT_PROMPT =\n \"Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.\";\n\n/**\n * The shape both entry points ask the model for. Exported as the bare zod schema rather than a\n * ready-made `Output.object(...)`: the AI SDK's `Output` type can't be named in emitted declarations\n * (TS4023), so each call site wraps this locally instead.\n */\nexport const aiEnrichmentSchema = z.object({\n tags: z.array(z.string()),\n description: z.string()\n});\n\n/** The enrichment result: what the model produces and what gets persisted. */\nexport interface IEnrichmentOutput {\n tags: string[];\n description: string;\n}\n\n/**\n * Everything needed to run the AI call, gathered up front: the image bytes, the resolved provider,\n * and the file's current tags (needed to merge rather than overwrite).\n *\n * Deliberately split from the AI call itself so both entry points share the same preparation and the\n * same failure modes — and so the streaming route can answer with a real HTTP status code for\n * anything knowable before the stream opens, instead of burying it in a 200 response.\n */\nexport interface IPreparedImageEnrichment {\n fileId: string;\n imageBase64: string;\n imageMediaType: string;\n model: string;\n connection: {\n sdkName: string;\n apiKey: string;\n };\n}\n\nexport interface IPrepareImageEnrichmentUseCase {\n execute(fileId: string): Promise<Result<IPreparedImageEnrichment, ImageEnrichmentError>>;\n}\n\nexport const PrepareImageEnrichmentUseCase = createAbstraction<IPrepareImageEnrichmentUseCase>(\n \"PrepareImageEnrichmentUseCase\"\n);\n\nexport namespace PrepareImageEnrichmentUseCase {\n export type Interface = IPrepareImageEnrichmentUseCase;\n}\n\nexport interface IApplyImageEnrichmentParams {\n fileId: string;\n tags: string[];\n description: string;\n}\n\nexport interface IAppliedImageEnrichment {\n fileId: string;\n tags: string[];\n description: string;\n}\n\nexport interface IApplyImageEnrichmentUseCase {\n execute(\n params: IApplyImageEnrichmentParams\n ): Promise<Result<IAppliedImageEnrichment, ImageEnrichmentError>>;\n}\n\nexport const ApplyImageEnrichmentUseCase = createAbstraction<IApplyImageEnrichmentUseCase>(\n \"ApplyImageEnrichmentUseCase\"\n);\n\nexport namespace ApplyImageEnrichmentUseCase {\n export type Interface = IApplyImageEnrichmentUseCase;\n}\n"],"names":["AI_ENRICHMENT_PROMPT","aiEnrichmentSchema","z","PrepareImageEnrichmentUseCase","createAbstraction","ApplyImageEnrichmentUseCase"],"mappings":";;AAIO,MAAMA,uBACT;AAOG,MAAMC,qBAAqBC,EAAE,MAAM,CAAC;IACvC,MAAMA,EAAE,KAAK,CAACA,EAAE,MAAM;IACtB,aAAaA,EAAE,MAAM;AACzB;AA+BO,MAAMC,gCAAgCC,kBACzC;AAyBG,MAAMC,8BAA8BD,kBACvC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Ai } from "@webiny/api-core/features/ai/index.js";
|
|
2
|
+
import type { IPreparedImageEnrichment } from "./abstractions.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds the AI request both enrichment entry points send — the background task via `generateText`
|
|
5
|
+
* and the streaming route via `streamText`. Identical model, structured output, connection, and
|
|
6
|
+
* message payload; only the call differs.
|
|
7
|
+
*
|
|
8
|
+
* This is also where `Output.object(...)` lives rather than on `IPreparedImageEnrichment`: the AI
|
|
9
|
+
* SDK's `Output` type can't be named in emitted declarations (TS4023), so it has to be built inside a
|
|
10
|
+
* function body, not held on an exported interface.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildEnrichmentAiRequest(prepared: IPreparedImageEnrichment): Ai.GenerateTextParams;
|