@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.
Files changed (195) hide show
  1. package/AiPowerups.js +2 -2
  2. package/AiPowerups.js.map +1 -1
  3. package/admin/Extension.js +10 -2
  4. package/admin/Extension.js.map +1 -1
  5. package/admin/features/compareEntryRevisions/CompareEntryRevisionsGateway.d.ts +12 -0
  6. package/admin/features/compareEntryRevisions/CompareEntryRevisionsGateway.js +46 -0
  7. package/admin/features/compareEntryRevisions/CompareEntryRevisionsGateway.js.map +1 -0
  8. package/admin/features/compareEntryRevisions/CompareEntryRevisionsRepository.d.ts +11 -0
  9. package/admin/features/compareEntryRevisions/CompareEntryRevisionsRepository.js +18 -0
  10. package/admin/features/compareEntryRevisions/CompareEntryRevisionsRepository.js.map +1 -0
  11. package/admin/features/compareEntryRevisions/CompareEntryRevisionsUseCase.d.ts +11 -0
  12. package/admin/features/compareEntryRevisions/CompareEntryRevisionsUseCase.js +18 -0
  13. package/admin/features/compareEntryRevisions/CompareEntryRevisionsUseCase.js.map +1 -0
  14. package/admin/features/compareEntryRevisions/abstractions.d.ts +32 -0
  15. package/admin/features/compareEntryRevisions/abstractions.js +7 -0
  16. package/admin/features/compareEntryRevisions/abstractions.js.map +1 -0
  17. package/admin/features/compareEntryRevisions/feature.d.ts +1 -0
  18. package/admin/features/compareEntryRevisions/feature.js +15 -0
  19. package/admin/features/compareEntryRevisions/feature.js.map +1 -0
  20. package/admin/features/compareEntryRevisions/index.d.ts +2 -0
  21. package/admin/features/compareEntryRevisions/index.js +2 -0
  22. package/admin/features/feature.js +2 -0
  23. package/admin/features/feature.js.map +1 -1
  24. package/admin/features/generateEntryContent/GenerateEntryContentGateway.js +2 -0
  25. package/admin/features/generateEntryContent/GenerateEntryContentGateway.js.map +1 -1
  26. package/admin/features/generateEntryContent/abstractions.d.ts +1 -0
  27. package/admin/features/generateEntryContent/abstractions.js.map +1 -1
  28. package/admin/features/generatePageContent/GeneratePageContentGateway.js +2 -0
  29. package/admin/features/generatePageContent/GeneratePageContentGateway.js.map +1 -1
  30. package/admin/features/generatePageContent/abstractions.d.ts +1 -0
  31. package/admin/features/generatePageContent/abstractions.js.map +1 -1
  32. package/admin/presentation/AiPowerUpsSettings/AiPowerUpsSettingsPage.d.ts +2 -2
  33. package/admin/presentation/AiPromptFormFactory/AiPromptFormFactory.d.ts +20 -0
  34. package/admin/presentation/AiPromptFormFactory/AiPromptFormFactory.js +108 -0
  35. package/admin/presentation/AiPromptFormFactory/AiPromptFormFactory.js.map +1 -0
  36. package/admin/presentation/AiPromptFormFactory/abstractions.d.ts +14 -0
  37. package/admin/presentation/AiPromptFormFactory/abstractions.js +5 -0
  38. package/admin/presentation/AiPromptFormFactory/abstractions.js.map +1 -0
  39. package/admin/presentation/AiPromptFormFactory/feature.d.ts +1 -0
  40. package/admin/presentation/AiPromptFormFactory/feature.js +11 -0
  41. package/admin/presentation/AiPromptFormFactory/feature.js.map +1 -0
  42. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDialog.d.ts +9 -0
  43. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDialog.js +118 -0
  44. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDialog.js.map +1 -0
  45. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDrawer.d.ts +9 -0
  46. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDrawer.js +72 -0
  47. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDrawer.js.map +1 -0
  48. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsMenuItem.d.ts +4 -0
  49. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsMenuItem.js +21 -0
  50. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsMenuItem.js.map +1 -0
  51. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsOverlays.d.ts +2 -0
  52. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsOverlays.js +17 -0
  53. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsOverlays.js.map +1 -0
  54. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsPresenter.d.ts +25 -0
  55. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsPresenter.js +90 -0
  56. package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsPresenter.js.map +1 -0
  57. package/admin/presentation/CmsCompareEntryRevisions/Extension.d.ts +2 -0
  58. package/admin/presentation/CmsCompareEntryRevisions/Extension.js +16 -0
  59. package/admin/presentation/CmsCompareEntryRevisions/Extension.js.map +1 -0
  60. package/admin/presentation/CmsCompareEntryRevisions/abstractions.d.ts +29 -0
  61. package/admin/presentation/CmsCompareEntryRevisions/abstractions.js +5 -0
  62. package/admin/presentation/CmsCompareEntryRevisions/abstractions.js.map +1 -0
  63. package/admin/presentation/CmsCompareEntryRevisions/feature.d.ts +3 -0
  64. package/admin/presentation/CmsCompareEntryRevisions/feature.js +17 -0
  65. package/admin/presentation/CmsCompareEntryRevisions/feature.js.map +1 -0
  66. package/admin/presentation/CmsContentGeneration/CmsGenerateContentButton.d.ts +2 -2
  67. package/admin/presentation/CmsContentGeneration/CmsGenerateContentDialog.d.ts +2 -2
  68. package/admin/presentation/CmsContentGeneration/CmsGenerateContentPresenter.d.ts +4 -12
  69. package/admin/presentation/CmsContentGeneration/CmsGenerateContentPresenter.js +12 -94
  70. package/admin/presentation/CmsContentGeneration/CmsGenerateContentPresenter.js.map +1 -1
  71. package/admin/presentation/CmsContentGeneration/Extension.d.ts +1 -1
  72. package/admin/presentation/CmsContentGeneration/Extension.js +9 -5
  73. package/admin/presentation/CmsContentGeneration/Extension.js.map +1 -1
  74. package/admin/presentation/CmsEntryWizard/CmsEntryWizard.d.ts +4 -0
  75. package/admin/presentation/CmsEntryWizard/CmsEntryWizard.js +113 -0
  76. package/admin/presentation/CmsEntryWizard/CmsEntryWizard.js.map +1 -0
  77. package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.d.ts +4 -0
  78. package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js +19 -0
  79. package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js.map +1 -0
  80. package/admin/presentation/CmsEntryWizard/Extension.d.ts +2 -0
  81. package/admin/presentation/CmsEntryWizard/Extension.js +10 -0
  82. package/admin/presentation/CmsEntryWizard/Extension.js.map +1 -0
  83. package/admin/presentation/ProvidersSettings.js +1 -1
  84. package/admin/presentation/ProvidersSettings.js.map +1 -1
  85. package/admin/presentation/WbContentGeneration/GenerateContentDialog.d.ts +2 -2
  86. package/admin/presentation/WbContentGeneration/GenerateContentDialog.js +2 -2
  87. package/admin/presentation/WbContentGeneration/GenerateContentDialog.js.map +1 -1
  88. package/admin/presentation/WbContentGeneration/GenerateContentPresenter.d.ts +4 -12
  89. package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js +12 -94
  90. package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js.map +1 -1
  91. package/api/Extension.js +8 -0
  92. package/api/Extension.js.map +1 -1
  93. package/api/features/AiChatProvider/PowerUpsAiChatProvider.d.ts +24 -0
  94. package/api/features/AiChatProvider/PowerUpsAiChatProvider.js +31 -0
  95. package/api/features/AiChatProvider/PowerUpsAiChatProvider.js.map +1 -0
  96. package/api/features/AiChatProvider/feature.d.ts +9 -0
  97. package/api/features/AiChatProvider/feature.js +11 -0
  98. package/api/features/AiChatProvider/feature.js.map +1 -0
  99. package/api/features/AiChatProvider/index.d.ts +2 -0
  100. package/api/features/AiChatProvider/index.js +2 -0
  101. package/api/features/AiImageEnrichment/AiImageEnrichmentAfterCreateHandler.d.ts +1 -3
  102. package/api/features/AiImageEnrichment/AiImageEnrichmentAfterCreateHandler.js +2 -6
  103. package/api/features/AiImageEnrichment/AiImageEnrichmentAfterCreateHandler.js.map +1 -1
  104. package/api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.d.ts +36 -0
  105. package/api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.js +85 -0
  106. package/api/features/AiImageEnrichment/AiImageEnrichmentStreamRoute.js.map +1 -0
  107. package/api/features/AiImageEnrichment/AiImageEnrichmentTask.d.ts +10 -16
  108. package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js +27 -102
  109. package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js.map +1 -1
  110. package/api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.d.ts +18 -0
  111. package/api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.js +50 -0
  112. package/api/features/AiImageEnrichment/ApplyImageEnrichmentUseCase.js.map +1 -0
  113. package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.d.ts +19 -0
  114. package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js +49 -0
  115. package/api/features/AiImageEnrichment/PrepareImageEnrichmentUseCase.js.map +1 -0
  116. package/api/features/AiImageEnrichment/abstractions.d.ts +60 -0
  117. package/api/features/AiImageEnrichment/abstractions.js +12 -0
  118. package/api/features/AiImageEnrichment/abstractions.js.map +1 -0
  119. package/api/features/AiImageEnrichment/buildEnrichmentAiRequest.d.ts +12 -0
  120. package/api/features/AiImageEnrichment/buildEnrichmentAiRequest.js +30 -0
  121. package/api/features/AiImageEnrichment/buildEnrichmentAiRequest.js.map +1 -0
  122. package/api/features/AiImageEnrichment/errors.d.ts +26 -0
  123. package/api/features/AiImageEnrichment/errors.js +28 -0
  124. package/api/features/AiImageEnrichment/errors.js.map +1 -0
  125. package/api/features/AiImageEnrichment/feature.js +9 -0
  126. package/api/features/AiImageEnrichment/feature.js.map +1 -1
  127. package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.d.ts +6 -0
  128. package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.js +9 -0
  129. package/api/features/AiImageEnrichment/imageEnrichmentErrorStatusCode.js.map +1 -0
  130. package/api/features/AiImageEnrichment/readEnrichmentPartial.d.ts +8 -0
  131. package/api/features/AiImageEnrichment/readEnrichmentPartial.js +16 -0
  132. package/api/features/AiImageEnrichment/readEnrichmentPartial.js.map +1 -0
  133. package/api/features/AiImageEnrichment/streamEvents.d.ts +36 -0
  134. package/api/features/AiImageEnrichment/streamEvents.js +0 -0
  135. package/api/features/AiPromptContext/AiPromptContextBuilder.d.ts +8 -1
  136. package/api/features/AiPromptContext/AiPromptContextBuilder.js +46 -2
  137. package/api/features/AiPromptContext/AiPromptContextBuilder.js.map +1 -1
  138. package/api/features/AiPromptContext/abstractions.d.ts +3 -0
  139. package/api/features/AiPromptContext/abstractions.js +9 -1
  140. package/api/features/AiPromptContext/abstractions.js.map +1 -1
  141. package/api/features/AiPromptContext/index.d.ts +1 -1
  142. package/api/features/AiPromptContext/index.js +1 -1
  143. package/api/features/CmsCompareEntryRevisions/CmsCompareEntryRevisionsUseCase.d.ts +22 -0
  144. package/api/features/CmsCompareEntryRevisions/CmsCompareEntryRevisionsUseCase.js +136 -0
  145. package/api/features/CmsCompareEntryRevisions/CmsCompareEntryRevisionsUseCase.js.map +1 -0
  146. package/api/features/CmsCompareEntryRevisions/abstractions.d.ts +18 -0
  147. package/api/features/CmsCompareEntryRevisions/abstractions.js +5 -0
  148. package/api/features/CmsCompareEntryRevisions/abstractions.js.map +1 -0
  149. package/api/features/CmsCompareEntryRevisions/feature.d.ts +4 -0
  150. package/api/features/CmsCompareEntryRevisions/feature.js +11 -0
  151. package/api/features/CmsCompareEntryRevisions/feature.js.map +1 -0
  152. package/api/features/CmsCompareEntryRevisions/index.d.ts +2 -0
  153. package/api/features/CmsCompareEntryRevisions/index.js +2 -0
  154. package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentTask.d.ts +1 -0
  155. package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentTask.js +20 -10
  156. package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentTask.js.map +1 -1
  157. package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentUseCase.d.ts +1 -1
  158. package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentUseCase.js +5 -5
  159. package/api/features/CmsGenerateEntryContent/CmsGenerateEntryContentUseCase.js.map +1 -1
  160. package/api/features/CmsGenerateEntryContent/abstractions.d.ts +10 -3
  161. package/api/features/CmsGenerateEntryContent/abstractions.js.map +1 -1
  162. package/api/features/GetSettings/index.d.ts +1 -0
  163. package/api/features/GetSettings/index.js +1 -0
  164. package/api/features/Providers/types.d.ts +2 -2
  165. package/api/features/WbGeneratePageContent/WbGeneratePageContentTask.d.ts +1 -0
  166. package/api/features/WbGeneratePageContent/WbGeneratePageContentTask.js +19 -9
  167. package/api/features/WbGeneratePageContent/WbGeneratePageContentTask.js.map +1 -1
  168. package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js +4 -3
  169. package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js.map +1 -1
  170. package/api/features/WbGeneratePageContent/abstractions.d.ts +1 -0
  171. package/api/features/WbGeneratePageContent/abstractions.js.map +1 -1
  172. package/api/features/WbTranslatePage/LexicalParser.d.ts +10 -0
  173. package/api/features/WbTranslatePage/LexicalParser.js +37 -0
  174. package/api/features/WbTranslatePage/LexicalParser.js.map +1 -0
  175. package/api/features/WbTranslatePage/WbTranslatePageDecorator.d.ts +27 -0
  176. package/api/features/WbTranslatePage/WbTranslatePageDecorator.js +134 -0
  177. package/api/features/WbTranslatePage/WbTranslatePageDecorator.js.map +1 -0
  178. package/api/features/WbTranslatePage/abstractions/LexicalParser.d.ts +7 -0
  179. package/api/features/WbTranslatePage/abstractions/LexicalParser.js +5 -0
  180. package/api/features/WbTranslatePage/abstractions/LexicalParser.js.map +1 -0
  181. package/api/features/WbTranslatePage/feature.d.ts +4 -0
  182. package/api/features/WbTranslatePage/feature.js +13 -0
  183. package/api/features/WbTranslatePage/feature.js.map +1 -0
  184. package/api/features/WbTranslatePage/index.d.ts +2 -0
  185. package/api/features/WbTranslatePage/index.js +2 -0
  186. package/api/graphql/BaseGraphQLSchema.js +11 -4
  187. package/api/graphql/BaseGraphQLSchema.js.map +1 -1
  188. package/api/graphql/CmsCompareEntryRevisionsSchema.d.ts +8 -0
  189. package/api/graphql/CmsCompareEntryRevisionsSchema.js +68 -0
  190. package/api/graphql/CmsCompareEntryRevisionsSchema.js.map +1 -0
  191. package/exports/admin/ai-powerups.d.ts +1 -0
  192. package/exports/admin/ai-powerups.js +1 -0
  193. package/exports/api/ai-powerups.d.ts +2 -0
  194. package/exports/api/ai-powerups.js +2 -0
  195. 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 { Encryption } from "@webiny/api-core/features/encryption/index.js";
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 getFile;
16
- private getFileContents;
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(getFile: GetFileUseCase.Interface, getFileContents: GetFileContentsByIdUseCase.Interface, updateFile: UpdateFileUseCase.Interface, ai: Ai.Interface, getSettings: GetSettingsUseCase.Interface, encryption: Encryption.Interface, identityContext: IdentityContext.Interface, sendToIdentity: WebsocketsSendToIdentityUseCase.Interface);
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 { Encryption } from "@webiny/api-core/features/encryption/index.js";
6
- import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
7
- import { GetFileContentsByIdUseCase } from "@webiny/api-file-manager/features/file/GetFileContentsById/abstractions.js";
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(getFile, getFileContents, updateFile, ai, getSettings, encryption, identityContext, sendToIdentity){
22
- this.getFile = getFile;
23
- this.getFileContents = getFileContents;
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 fileResult = await this.getFile.execute(input.fileId);
44
- if (fileResult.isFail()) return controller.response.error({
45
- message: `File not found: ${input.fileId}`
46
- });
47
- const file = fileResult.value;
48
- if (!file.type.startsWith("image/")) return controller.response.done("File is not an image; skipping AI enrichment.");
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 mergedTags = [
100
- ...new Set([
101
- ...file.tags,
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 (updateResult.isFail()) return controller.response.error({
111
- message: `Failed to update file: ${updateResult.error.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
- GetFileUseCase,
131
- GetFileContentsByIdUseCase,
132
- UpdateFileUseCase,
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;