@vertesia/workflow 0.51.0 → 0.52.0
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/lib/cjs/activities/advanced/createOrUpdateDocumentFromInteractionRun.js +7 -1
- package/lib/cjs/activities/advanced/createOrUpdateDocumentFromInteractionRun.js.map +1 -1
- package/lib/cjs/activities/chunkDocument.js +39 -34
- package/lib/cjs/activities/chunkDocument.js.map +1 -1
- package/lib/cjs/activities/createDocumentFromOther.js +2 -2
- package/lib/cjs/activities/createDocumentFromOther.js.map +1 -1
- package/lib/cjs/activities/executeInteraction.js +11 -5
- package/lib/cjs/activities/executeInteraction.js.map +1 -1
- package/lib/cjs/activities/extractDocumentText.js +24 -6
- package/lib/cjs/activities/extractDocumentText.js.map +1 -1
- package/lib/cjs/activities/generateDocumentProperties.js +22 -4
- package/lib/cjs/activities/generateDocumentProperties.js.map +1 -1
- package/lib/cjs/activities/generateEmbeddings.js +58 -102
- package/lib/cjs/activities/generateEmbeddings.js.map +1 -1
- package/lib/cjs/activities/generateImageRendition.js +77 -34
- package/lib/cjs/activities/generateImageRendition.js.map +1 -1
- package/lib/cjs/activities/generateOrAssignContentType.js +3 -7
- package/lib/cjs/activities/generateOrAssignContentType.js.map +1 -1
- package/lib/cjs/activities/notifyWebhook.js.map +1 -1
- package/lib/cjs/conversion/image.js +80 -12
- package/lib/cjs/conversion/image.js.map +1 -1
- package/lib/cjs/dsl/setup/ActivityContext.js +30 -6
- package/lib/cjs/dsl/setup/ActivityContext.js.map +1 -1
- package/lib/cjs/dsl.js +1 -1
- package/lib/cjs/dsl.js.map +1 -1
- package/lib/cjs/errors.js +13 -1
- package/lib/cjs/errors.js.map +1 -1
- package/lib/cjs/iterative-generation/iterativeGenerationWorkflow.js +2 -1
- package/lib/cjs/iterative-generation/iterativeGenerationWorkflow.js.map +1 -1
- package/lib/cjs/system/notifyWebhookWorkflow.js +2 -1
- package/lib/cjs/system/notifyWebhookWorkflow.js.map +1 -1
- package/lib/cjs/system/recalculateEmbeddingsWorkflow.js +1 -1
- package/lib/cjs/system/recalculateEmbeddingsWorkflow.js.map +1 -1
- package/lib/cjs/utils/blobs.js +12 -6
- package/lib/cjs/utils/blobs.js.map +1 -1
- package/lib/cjs/utils/chunks.js +14 -0
- package/lib/cjs/utils/chunks.js.map +1 -0
- package/lib/cjs/utils/client.js +4 -3
- package/lib/cjs/utils/client.js.map +1 -1
- package/lib/cjs/utils/memory.js +2 -9
- package/lib/cjs/utils/memory.js.map +1 -1
- package/lib/esm/activities/advanced/createOrUpdateDocumentFromInteractionRun.js +7 -1
- package/lib/esm/activities/advanced/createOrUpdateDocumentFromInteractionRun.js.map +1 -1
- package/lib/esm/activities/chunkDocument.js +39 -34
- package/lib/esm/activities/chunkDocument.js.map +1 -1
- package/lib/esm/activities/createDocumentFromOther.js +1 -1
- package/lib/esm/activities/createDocumentFromOther.js.map +1 -1
- package/lib/esm/activities/executeInteraction.js +11 -5
- package/lib/esm/activities/executeInteraction.js.map +1 -1
- package/lib/esm/activities/extractDocumentText.js +24 -6
- package/lib/esm/activities/extractDocumentText.js.map +1 -1
- package/lib/esm/activities/generateDocumentProperties.js +22 -4
- package/lib/esm/activities/generateDocumentProperties.js.map +1 -1
- package/lib/esm/activities/generateEmbeddings.js +58 -69
- package/lib/esm/activities/generateEmbeddings.js.map +1 -1
- package/lib/esm/activities/generateImageRendition.js +78 -35
- package/lib/esm/activities/generateImageRendition.js.map +1 -1
- package/lib/esm/activities/generateOrAssignContentType.js +3 -7
- package/lib/esm/activities/generateOrAssignContentType.js.map +1 -1
- package/lib/esm/activities/notifyWebhook.js.map +1 -1
- package/lib/esm/conversion/image.js +80 -12
- package/lib/esm/conversion/image.js.map +1 -1
- package/lib/esm/dsl/setup/ActivityContext.js +31 -7
- package/lib/esm/dsl/setup/ActivityContext.js.map +1 -1
- package/lib/esm/dsl.js +1 -1
- package/lib/esm/dsl.js.map +1 -1
- package/lib/esm/errors.js +11 -0
- package/lib/esm/errors.js.map +1 -1
- package/lib/esm/iterative-generation/iterativeGenerationWorkflow.js +2 -1
- package/lib/esm/iterative-generation/iterativeGenerationWorkflow.js.map +1 -1
- package/lib/esm/system/notifyWebhookWorkflow.js +2 -1
- package/lib/esm/system/notifyWebhookWorkflow.js.map +1 -1
- package/lib/esm/system/recalculateEmbeddingsWorkflow.js +2 -2
- package/lib/esm/system/recalculateEmbeddingsWorkflow.js.map +1 -1
- package/lib/esm/utils/blobs.js +12 -6
- package/lib/esm/utils/blobs.js.map +1 -1
- package/lib/esm/utils/chunks.js +9 -0
- package/lib/esm/utils/chunks.js.map +1 -0
- package/lib/esm/utils/client.js +4 -3
- package/lib/esm/utils/client.js.map +1 -1
- package/lib/esm/utils/memory.js +2 -7
- package/lib/esm/utils/memory.js.map +1 -1
- package/lib/types/activities/advanced/createOrUpdateDocumentFromInteractionRun.d.ts +10 -0
- package/lib/types/activities/advanced/createOrUpdateDocumentFromInteractionRun.d.ts.map +1 -1
- package/lib/types/activities/chunkDocument.d.ts +15 -0
- package/lib/types/activities/chunkDocument.d.ts.map +1 -1
- package/lib/types/activities/createDocumentFromOther.d.ts.map +1 -1
- package/lib/types/activities/executeInteraction.d.ts +14 -3
- package/lib/types/activities/executeInteraction.d.ts.map +1 -1
- package/lib/types/activities/generateDocumentProperties.d.ts +1 -1
- package/lib/types/activities/generateDocumentProperties.d.ts.map +1 -1
- package/lib/types/activities/generateEmbeddings.d.ts +21 -17
- package/lib/types/activities/generateEmbeddings.d.ts.map +1 -1
- package/lib/types/activities/generateImageRendition.d.ts +3 -5
- package/lib/types/activities/generateImageRendition.d.ts.map +1 -1
- package/lib/types/activities/generateOrAssignContentType.d.ts.map +1 -1
- package/lib/types/activities/notifyWebhook.d.ts +1 -2
- package/lib/types/activities/notifyWebhook.d.ts.map +1 -1
- package/lib/types/conversion/image.d.ts +8 -6
- package/lib/types/conversion/image.d.ts.map +1 -1
- package/lib/types/dsl/setup/ActivityContext.d.ts +3 -0
- package/lib/types/dsl/setup/ActivityContext.d.ts.map +1 -1
- package/lib/types/dsl.d.ts +1 -1
- package/lib/types/dsl.d.ts.map +1 -1
- package/lib/types/errors.d.ts +6 -0
- package/lib/types/errors.d.ts.map +1 -1
- package/lib/types/iterative-generation/iterativeGenerationWorkflow.d.ts.map +1 -1
- package/lib/types/system/notifyWebhookWorkflow.d.ts.map +1 -1
- package/lib/types/system/recalculateEmbeddingsWorkflow.d.ts +2 -17
- package/lib/types/system/recalculateEmbeddingsWorkflow.d.ts.map +1 -1
- package/lib/types/utils/blobs.d.ts.map +1 -1
- package/lib/types/utils/chunks.d.ts +9 -0
- package/lib/types/utils/chunks.d.ts.map +1 -0
- package/lib/types/utils/client.d.ts.map +1 -1
- package/lib/types/utils/memory.d.ts +1 -5
- package/lib/types/utils/memory.d.ts.map +1 -1
- package/lib/workflows-bundle.js +15394 -14602
- package/package.json +8 -6
- package/src/activities/advanced/createOrUpdateDocumentFromInteractionRun.ts +20 -1
- package/src/activities/chunkDocument.ts +62 -42
- package/src/activities/createDocumentFromOther.ts +1 -1
- package/src/activities/executeInteraction.ts +27 -9
- package/src/activities/extractDocumentText.ts +28 -7
- package/src/activities/generateDocumentProperties.ts +37 -16
- package/src/activities/generateEmbeddings.ts +91 -79
- package/src/activities/generateImageRendition.ts +100 -53
- package/src/activities/generateOrAssignContentType.ts +5 -11
- package/src/activities/notifyWebhook.ts +2 -2
- package/src/conversion/image.test.ts +110 -18
- package/src/conversion/image.ts +90 -15
- package/src/conversion/pandoc.test.ts +7 -5
- package/src/dsl/setup/ActivityContext.ts +57 -16
- package/src/dsl.ts +1 -1
- package/src/errors.ts +27 -6
- package/src/iterative-generation/iterativeGenerationWorkflow.ts +2 -1
- package/src/system/notifyWebhookWorkflow.ts +2 -1
- package/src/system/recalculateEmbeddingsWorkflow.ts +2 -2
- package/src/utils/blobs.ts +11 -6
- package/src/utils/chunks.ts +17 -0
- package/src/utils/client.ts +4 -3
- package/src/utils/memory.ts +3 -8
@@ -1,19 +1,42 @@
|
|
1
|
-
import { VertesiaClient } from "@vertesia/client";
|
2
|
-
import { ContentObject, DSLActivityExecutionPayload, DSLActivitySpec, ProjectConfigurationEmbeddings, SupportedEmbeddingTypes } from "@vertesia/common";
|
3
1
|
import { EmbeddingsResult } from "@llumiverse/core";
|
4
2
|
import { log } from "@temporalio/activity";
|
5
|
-
import
|
3
|
+
import { VertesiaClient } from "@vertesia/client";
|
4
|
+
import { ContentObject, DSLActivityExecutionPayload, DSLActivitySpec, ProjectConfigurationEmbeddings, SupportedEmbeddingTypes } from "@vertesia/common";
|
6
5
|
import { setupActivity } from "../dsl/setup/ActivityContext.js";
|
7
6
|
import { NoDocumentFound } from '../errors.js';
|
8
7
|
import { fetchBlobAsBase64, md5 } from "../utils/blobs.js";
|
8
|
+
import { DocPart, getContentParts } from "../utils/chunks.js";
|
9
9
|
import { countTokens } from "../utils/tokens.js";
|
10
10
|
|
11
11
|
|
12
12
|
export interface GenerateEmbeddingsParams {
|
13
|
+
|
14
|
+
/**
|
15
|
+
* The model to use for embedding generation
|
16
|
+
* If not set, the default model for the project will be used
|
17
|
+
*/
|
13
18
|
model?: string;
|
19
|
+
|
20
|
+
/**
|
21
|
+
* The environment to use for embedding generation
|
22
|
+
* If not set, the default environment for the project will be used
|
23
|
+
*/
|
14
24
|
environment?: string;
|
25
|
+
|
26
|
+
/**
|
27
|
+
* If true, force embedding generation even if the document already has embeddings
|
28
|
+
*/
|
15
29
|
force?: boolean;
|
30
|
+
|
31
|
+
/**
|
32
|
+
* The embedding type to generate
|
33
|
+
*/
|
16
34
|
type: SupportedEmbeddingTypes;
|
35
|
+
|
36
|
+
/**
|
37
|
+
* The DocParts to use for long documents
|
38
|
+
*/
|
39
|
+
parts?: DocPart[];
|
17
40
|
}
|
18
41
|
|
19
42
|
export interface GenerateEmbeddings extends DSLActivitySpec<GenerateEmbeddingsParams> {
|
@@ -103,7 +126,7 @@ interface ExecuteGenerateEmbeddingsParams {
|
|
103
126
|
force?: boolean;
|
104
127
|
}
|
105
128
|
|
106
|
-
async function generateTextEmbeddings({ document, client, type, config }: ExecuteGenerateEmbeddingsParams) {
|
129
|
+
async function generateTextEmbeddings({ document, client, type, config }: ExecuteGenerateEmbeddingsParams, parts?: DocPart[],) {
|
107
130
|
// if (!force && document.embeddings[type]?.etag === (document.text_etag ?? md5(document.text))) {
|
108
131
|
// return { id: objectId, status: "skipped", message: "embeddings already generated" }
|
109
132
|
// }
|
@@ -125,6 +148,8 @@ async function generateTextEmbeddings({ document, client, type, config }: Execut
|
|
125
148
|
|
126
149
|
const { environment, model } = config;
|
127
150
|
|
151
|
+
const partDefinitions = parts ?? [];
|
152
|
+
|
128
153
|
// Count tokens if not already done
|
129
154
|
if (!document.tokens?.count && type === SupportedEmbeddingTypes.text) {
|
130
155
|
log.debug('Updating token count for document: ' + document.id);
|
@@ -150,79 +175,64 @@ async function generateTextEmbeddings({ document, client, type, config }: Execut
|
|
150
175
|
if (type === SupportedEmbeddingTypes.text && document.tokens?.count && document.tokens?.count > maxTokens) {
|
151
176
|
log.info('Document too large, generating embeddings for parts');
|
152
177
|
|
153
|
-
|
154
|
-
|
178
|
+
|
179
|
+
if (!document.text) {
|
180
|
+
return { id: document.id, status: "failed", message: "no text found" }
|
155
181
|
}
|
156
182
|
|
157
|
-
|
158
|
-
|
183
|
+
if (!partDefinitions || partDefinitions.length === 0) {
|
184
|
+
log.info('No parts found for document, skipping embeddings generation');
|
185
|
+
return { id: document.id, status: "failed", message: "no parts found" }
|
186
|
+
}
|
159
187
|
|
160
|
-
const generatePartEmbeddings = async (part: ContentObject<any>, i: number) => {
|
161
|
-
try {
|
162
|
-
log.info(`Generating embeddings for part ${part.id}`, { text_len: part.text?.length })
|
163
|
-
if (!part.text) {
|
164
|
-
return { id: part.id, number: i, result: null, status: "skipped", message: "no text found" }
|
165
|
-
}
|
166
188
|
|
167
|
-
|
168
|
-
|
169
|
-
|
189
|
+
log.info('Generating embeddings for parts', { parts: partDefinitions, max_tokens: maxTokens });
|
190
|
+
const docParts = getContentParts(document.text, partDefinitions);
|
191
|
+
|
192
|
+
|
193
|
+
log.info(`Retrieved ${docParts.length} parts`)
|
194
|
+
const start = new Date().getTime();
|
195
|
+
const generatePartEmbeddings = async (partContent: string, i: number) => {
|
196
|
+
const localStart = new Date().getTime();
|
197
|
+
try {
|
198
|
+
log.info(`Generating embeddings for part ${i}`, { text_len: partContent.length })
|
199
|
+
if (!partContent) {
|
200
|
+
return { id: i, number: i, result: null, status: "skipped", message: "no text found" }
|
170
201
|
}
|
171
202
|
|
172
|
-
const e = await generateEmbeddingsFromStudio(
|
173
|
-
log.error('Error generating embeddings for part'
|
203
|
+
const e = await generateEmbeddingsFromStudio(partContent, environment, client, model).catch(e => {
|
204
|
+
log.error('Error generating embeddings for part ' + i, { text_length: partContent.length, error: e });
|
174
205
|
return null;
|
175
206
|
});
|
176
207
|
|
177
208
|
if (!e || !e.values) {
|
178
|
-
return { id:
|
209
|
+
return { id: i, number: i, result: null, message: "no embeddings generated" }
|
179
210
|
}
|
180
211
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
}).catch(err => {
|
188
|
-
log.info(`Error updating embeddings on part ${part.id}`);
|
189
|
-
return { id: part.id, number: i, result: null, message: "error setting embeddings on part", error: err.message }
|
190
|
-
})
|
191
|
-
|
192
|
-
log.info('Generated embeddings for part: ' + part.id);
|
193
|
-
return { id: part.id, number: i, result: e }
|
212
|
+
if (e.values.length === 0) {
|
213
|
+
return { id: i, number: i, result: null, message: "no embeddings generated" }
|
214
|
+
}
|
215
|
+
log.info(`Generated embeddings for part ${i}`, { len: e.values.length, duration: new Date().getTime() - localStart });
|
216
|
+
|
217
|
+
return { inumber: i, result: e }
|
194
218
|
} catch (err: any) {
|
195
|
-
log.info(`Error generating ${type} embeddings for part ${
|
196
|
-
return {
|
219
|
+
log.info(`Error generating ${type} embeddings for part ${i} of ${document.id}`, { error: err });
|
220
|
+
return { number: i, result: null, message: "error generating embeddings", error: err.message }
|
197
221
|
}
|
198
222
|
}
|
199
223
|
|
200
|
-
const
|
201
|
-
const
|
202
|
-
|
203
|
-
|
204
|
-
// log.info(`Processing part ${p.id}`)
|
205
|
-
// const r = await generatePartEmbeddings(p, i++);
|
206
|
-
// res.push(r)
|
207
|
-
// }
|
208
|
-
|
209
|
-
|
210
|
-
// Filter out parts without embeddings
|
211
|
-
const validEmbeddings = res.filter(item => item.result !== null) as { id: string, number: number, result: EmbeddingsResult }[];
|
212
|
-
|
213
|
-
// Compute the document-level embedding using TensorFlow for attention mechanism
|
214
|
-
log.info('Computing document-level embedding using TF');
|
215
|
-
const documentEmbedding = computeAttentionEmbedding(validEmbeddings.map(item => item.result.values));
|
216
|
-
|
217
|
-
// Save the document-level embedding
|
224
|
+
const partEmbeddings = await Promise.all(docParts.map((part, i) => generatePartEmbeddings(part, i)));
|
225
|
+
const validPartEmbeddings = partEmbeddings.filter(e => e.result !== null).map(e => e.result);
|
226
|
+
const averagedEmbedding = computeAttentionEmbedding(validPartEmbeddings.map(e => e.values));
|
227
|
+
log.info(`Averaged embeddings for document ${document.id} in ${(new Date().getTime() - start) / 1000} seconds`, { len: averagedEmbedding.length, count: validPartEmbeddings.length, max_tokens: maxTokens });
|
218
228
|
await client.objects.setEmbedding(document.id, type,
|
219
229
|
{
|
220
|
-
values:
|
221
|
-
model:
|
230
|
+
values: averagedEmbedding,
|
231
|
+
model: validPartEmbeddings[0].model,
|
222
232
|
etag: document.text_etag
|
223
233
|
}
|
224
234
|
);
|
225
|
-
|
235
|
+
log.info(`Object ${document.id} embedding set`, { type, len: averagedEmbedding.length });
|
226
236
|
|
227
237
|
} else {
|
228
238
|
log.info(`Generating ${type} embeddings for document`);
|
@@ -311,35 +321,37 @@ async function generateEmbeddingsFromStudio(text: string, env: string, client: V
|
|
311
321
|
|
312
322
|
}
|
313
323
|
|
314
|
-
|
315
|
-
|
316
|
-
|
324
|
+
//Simplified attention mechanism
|
325
|
+
// This is a naive implementation and should be replaced with a more sophisticated
|
326
|
+
// using tensorflow in a specific package
|
327
|
+
function computeAttentionEmbedding(chunkEmbeddings: number[][]): number[] {
|
328
|
+
if (chunkEmbeddings.length === 0) return [];
|
329
|
+
|
317
330
|
const start = new Date().getTime();
|
318
331
|
|
319
|
-
//
|
320
|
-
const
|
332
|
+
// Generate random attention weights
|
333
|
+
const attentionWeights = chunkEmbeddings.map(() => Math.random());
|
321
334
|
|
322
|
-
//
|
323
|
-
const
|
335
|
+
// Apply softmax to get attention scores
|
336
|
+
const expWeights = attentionWeights.map(w => Math.exp(w));
|
337
|
+
const sumExpWeights = expWeights.reduce((sum, val) => sum + val, 0);
|
338
|
+
const attentionScores = expWeights.map(w => w / sumExpWeights);
|
324
339
|
|
325
|
-
//
|
326
|
-
const
|
340
|
+
// Get embedding dimension
|
341
|
+
const embeddingDim = chunkEmbeddings[0].length;
|
327
342
|
|
328
|
-
//
|
329
|
-
const
|
330
|
-
const documentEmbeddingTensor = tf.sum(weightedEmbeddings, axis);
|
343
|
+
// Initialize document embedding
|
344
|
+
const documentEmbedding = new Array(embeddingDim).fill(0);
|
331
345
|
|
332
|
-
//
|
333
|
-
|
334
|
-
|
335
|
-
|
346
|
+
// Weighted sum of embeddings
|
347
|
+
for (let i = 0; i < chunkEmbeddings.length; i++) {
|
348
|
+
for (let j = 0; j < embeddingDim; j++) {
|
349
|
+
documentEmbedding[j] += chunkEmbeddings[i][j] * attentionScores[i];
|
350
|
+
}
|
351
|
+
}
|
336
352
|
|
337
|
-
|
338
|
-
|
339
|
-
attentionWeights.dispose();
|
340
|
-
attentionScores.dispose();
|
341
|
-
weightedEmbeddings.dispose();
|
342
|
-
documentEmbeddingTensor.dispose();
|
353
|
+
const duration = new Date().getTime() - start;
|
354
|
+
console.log(`Computed document embedding in ${duration}ms for ${chunkEmbeddings.length} chunks`);
|
343
355
|
|
344
356
|
return documentEmbedding;
|
345
|
-
}
|
357
|
+
}
|
@@ -1,31 +1,27 @@
|
|
1
|
-
import { DSLActivityExecutionPayload, DSLActivitySpec, RenditionProperties } from "@vertesia/common";
|
2
1
|
import { log } from "@temporalio/activity";
|
2
|
+
import { NodeStreamSource } from "@vertesia/client/node";
|
3
|
+
import { DSLActivityExecutionPayload, DSLActivitySpec, RenditionProperties } from "@vertesia/common";
|
3
4
|
import fs from 'fs';
|
4
|
-
import
|
5
|
+
import ffmpeg from 'fluent-ffmpeg';
|
6
|
+
import path from 'path';
|
7
|
+
import os from 'os';
|
5
8
|
import { imageResizer } from "../conversion/image.js";
|
6
|
-
import { pdfToImages } from "../conversion/mutool.js";
|
7
9
|
import { setupActivity } from "../dsl/setup/ActivityContext.js";
|
8
10
|
import { NoDocumentFound, WorkflowParamNotFound } from "../errors.js";
|
9
|
-
import {
|
10
|
-
|
11
|
+
import { saveBlobToTempFile } from "../utils/blobs.js";
|
12
|
+
|
11
13
|
interface GenerateImageRenditionParams {
|
12
14
|
max_hw: number; //maximum size of the longuest side of the image
|
13
|
-
format:
|
14
|
-
multi_page?: boolean; //if true, generate a multi-page rendition
|
15
|
+
format: string; //format of the output image
|
15
16
|
}
|
16
17
|
|
17
|
-
|
18
18
|
export interface GenerateImageRendition extends DSLActivitySpec<GenerateImageRenditionParams> {
|
19
|
-
|
20
|
-
name: 'generateImageRendition';
|
21
|
-
|
19
|
+
name: "generateImageRendition";
|
22
20
|
}
|
23
21
|
|
24
|
-
|
25
22
|
export async function generateImageRendition(payload: DSLActivityExecutionPayload<GenerateImageRenditionParams>) {
|
26
23
|
const { client, objectId, params } = await setupActivity<GenerateImageRenditionParams>(payload);
|
27
24
|
|
28
|
-
const supportedNonImageInputTypes = ['application/pdf']
|
29
25
|
const inputObject = await client.objects.retrieve(objectId).catch((err) => {
|
30
26
|
log.error(`Failed to retrieve document ${objectId}`, err);
|
31
27
|
if (err.response?.status === 404) {
|
@@ -33,7 +29,7 @@ export async function generateImageRendition(payload: DSLActivityExecutionPayloa
|
|
33
29
|
}
|
34
30
|
throw err;
|
35
31
|
});
|
36
|
-
const renditionType = await client.types.getTypeByName(
|
32
|
+
const renditionType = await client.types.getTypeByName("Rendition");
|
37
33
|
|
38
34
|
if (!params.format) {
|
39
35
|
log.error(`Format not found`);
|
@@ -50,85 +46,136 @@ export async function generateImageRendition(payload: DSLActivityExecutionPayloa
|
|
50
46
|
throw new NoDocumentFound(`Document ${objectId} has no source`, [objectId]);
|
51
47
|
}
|
52
48
|
|
53
|
-
if (!inputObject.content.type || (!inputObject.content.type?.startsWith(
|
54
|
-
log.error(`Document ${objectId} is not an image`);
|
55
|
-
throw new NoDocumentFound(`Document ${objectId} is not an image or
|
49
|
+
if (!inputObject.content.type || (!inputObject.content.type?.startsWith("image/") && !inputObject.content.type?.startsWith("video/"))) {
|
50
|
+
log.error(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`);
|
51
|
+
throw new NoDocumentFound(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`, [objectId]);
|
56
52
|
}
|
57
53
|
|
58
54
|
//array of rendition files to upload
|
59
55
|
let renditionPages: string[] = [];
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
57
|
+
if (inputObject.content.type.startsWith('image/')) {
|
58
|
+
const imageFile = await saveBlobToTempFile(client, inputObject.content.source);
|
59
|
+
log.info(`Image ${objectId} copied to ${imageFile}`);
|
60
|
+
renditionPages.push(imageFile);
|
61
|
+
} else if (inputObject.content.type.startsWith('video/')) {
|
62
|
+
const videoFile = await saveBlobToTempFile(client, inputObject.content.source);
|
63
|
+
const tempOutputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'video-rendition-'));
|
64
|
+
const thumbnailPath = path.join(tempOutputDir, 'thumbnail.png');
|
65
|
+
|
66
|
+
try {
|
67
|
+
// Extract a frame at 10% of the video duration
|
68
|
+
await new Promise<void>((resolve, reject) => {
|
69
|
+
ffmpeg.ffprobe(videoFile, (err, metadata) => {
|
70
|
+
if (err) {
|
71
|
+
log.error(`Failed to probe video metadata: ${err.message}`);
|
72
|
+
return reject(err);
|
73
|
+
}
|
74
|
+
|
75
|
+
const duration = metadata.format.duration || 0;
|
76
|
+
const timestamp = Math.max(0.1 * duration, 1);
|
77
|
+
|
78
|
+
ffmpeg(videoFile)
|
79
|
+
.screenshots({
|
80
|
+
timestamps: [timestamp],
|
81
|
+
filename: 'thumbnail.png',
|
82
|
+
folder: tempOutputDir,
|
83
|
+
size: `${params.max_hw}x?`
|
84
|
+
})
|
85
|
+
.on('end', () => {
|
86
|
+
log.info(`Video frame extraction complete for ${objectId}`);
|
87
|
+
resolve();
|
88
|
+
})
|
89
|
+
.on('error', (err) => {
|
90
|
+
log.error(`Error extracting frame from video: ${err.message}`);
|
91
|
+
reject(err);
|
92
|
+
});
|
93
|
+
});
|
94
|
+
});
|
95
|
+
|
96
|
+
if (fs.existsSync(thumbnailPath)) {
|
97
|
+
renditionPages.push(thumbnailPath);
|
98
|
+
} else {
|
99
|
+
throw new Error(`Failed to generate thumbnail for video ${objectId}`);
|
100
|
+
}
|
101
|
+
} catch (error) {
|
102
|
+
log.error(`Error generating image rendition for video: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
103
|
+
throw new Error(`Failed to generate image rendition for video: ${objectId}`);
|
68
104
|
}
|
69
|
-
renditionPages = [...pages];
|
70
|
-
} else if (inputObject.content.type.startsWith('image/')) {
|
71
|
-
const tmpFile = await saveBlobToTempFile(client, inputObject.content.source);
|
72
|
-
const filestats = fs.statSync(tmpFile);
|
73
|
-
log.info(`Image ${objectId} copied to ${tmpFile}`, { filestats });
|
74
|
-
renditionPages.push(tmpFile);
|
75
105
|
}
|
76
106
|
|
77
107
|
//generate rendition name, pass an index for multi parts
|
78
108
|
const getRenditionName = (index: number = 0) => {
|
79
109
|
const name = `renditions/${objectId}/${params.max_hw}/${index}.${params.format}`;
|
80
110
|
return name;
|
81
|
-
}
|
111
|
+
};
|
82
112
|
|
83
113
|
if (!renditionPages || !renditionPages.length) {
|
84
114
|
log.error(`Failed to generate rendition for ${objectId}`);
|
85
115
|
throw new Error(`Failed to generate rendition for ${objectId}`);
|
86
116
|
}
|
87
117
|
|
88
|
-
log.info(
|
118
|
+
log.info(
|
119
|
+
`Uploading rendition for ${objectId} with ${renditionPages.length} pages (max_hw: ${params.max_hw}, format: ${params.format})`,
|
120
|
+
{ renditionPages },
|
121
|
+
);
|
89
122
|
const uploads = renditionPages.map(async (page, i) => {
|
90
123
|
const pageId = getRenditionName(i);
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
124
|
+
let resizedImagePath = null;
|
125
|
+
|
126
|
+
try {
|
127
|
+
// Resize the image using ImageMagick
|
128
|
+
resizedImagePath = await imageResizer(page, params.max_hw, params.format);
|
129
|
+
|
130
|
+
// Create a read stream from the resized image file
|
131
|
+
const fileStream = fs.createReadStream(resizedImagePath);
|
132
|
+
|
133
|
+
const source = new NodeStreamSource(
|
134
|
+
fileStream,
|
135
|
+
pageId.split("/").pop() ?? "0." + params.format,
|
136
|
+
"image/" + params.format,
|
137
|
+
pageId,
|
138
|
+
);
|
139
|
+
|
140
|
+
log.info(
|
141
|
+
`Uploading rendition for ${objectId} page ${i} with max_hw: ${params.max_hw} and format: ${params.format}`,
|
142
|
+
);
|
143
|
+
|
144
|
+
const result = await client.objects.upload(source).catch((err) => {
|
145
|
+
log.error(`Failed to upload rendition for ${objectId} page ${i}`, { error: err });
|
146
|
+
return Promise.resolve(null);
|
147
|
+
});
|
148
|
+
|
149
|
+
return result;
|
150
|
+
} catch (error) {
|
151
|
+
log.error(`Failed to process rendition for ${objectId} page ${i}`, { error });
|
103
152
|
return Promise.resolve(null);
|
104
|
-
}
|
153
|
+
}
|
105
154
|
});
|
106
155
|
|
107
156
|
const uploaded = await Promise.all(uploads);
|
108
157
|
if (!uploaded || !uploaded.length || !uploaded[0]) {
|
109
158
|
log.error(`Failed to upload rendition for ${objectId}`);
|
110
|
-
throw new Error(`Failed to upload rendition for ${objectId}`);
|
159
|
+
throw new Error(`Failed to upload rendition for ${objectId} - upload object is empty`);
|
111
160
|
}
|
112
161
|
|
113
|
-
|
114
|
-
|
162
|
+
log.info(`Creating rendition for ${objectId} with max_hw: ${params.max_hw} and format: ${params.format}`, {
|
163
|
+
uploaded,
|
164
|
+
});
|
115
165
|
const rendition = await client.objects.create({
|
116
166
|
name: inputObject.name + ` [Rendition ${params.max_hw}]`,
|
117
167
|
type: renditionType.id,
|
118
168
|
parent: inputObject.id,
|
119
169
|
content: uploaded[0],
|
120
170
|
properties: {
|
121
|
-
mime_type:
|
171
|
+
mime_type: "image/" + params.format,
|
122
172
|
source_etag: inputObject.content.source,
|
123
173
|
height: params.max_hw,
|
124
174
|
width: params.max_hw,
|
125
|
-
|
126
|
-
total_parts: uploaded.length
|
127
|
-
} satisfies RenditionProperties
|
175
|
+
} satisfies RenditionProperties,
|
128
176
|
});
|
129
177
|
|
130
178
|
log.info(`Rendition ${rendition.id} created for ${objectId}`, { rendition });
|
131
179
|
|
132
180
|
return { id: rendition.id, format: params.format, status: "success" };
|
133
|
-
|
134
181
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { log } from "@temporalio/activity";
|
2
|
-
import { CreateContentObjectTypePayload, DSLActivityExecutionPayload, DSLActivitySpec } from "@vertesia/common";
|
2
|
+
import { ContentObjectTypeItem, CreateContentObjectTypePayload, DSLActivityExecutionPayload, DSLActivitySpec } from "@vertesia/common";
|
3
3
|
import { ActivityContext, setupActivity } from "../dsl/setup/ActivityContext.js";
|
4
4
|
import { TruncateSpec, truncByMaxTokens } from "../utils/tokens.js";
|
5
5
|
import { InteractionExecutionParams, executeInteractionFromActivity } from "./executeInteraction.js";
|
@@ -56,12 +56,7 @@ export async function generateOrAssignContentType(payload: DSLActivityExecutionP
|
|
56
56
|
const types = await client.types.list();
|
57
57
|
|
58
58
|
//make a list of all existing types, and add hints if any
|
59
|
-
const existing_types = types.
|
60
|
-
if (params.typesHint) {
|
61
|
-
const newHints = params.typesHint.filter((t: string) => !existing_types.includes(t));
|
62
|
-
existing_types.push(...newHints);
|
63
|
-
}
|
64
|
-
|
59
|
+
const existing_types = types.filter(t => !["DocumentPart", "Rendition"].includes(t.name));
|
65
60
|
const content = object.text ? truncByMaxTokens(object.text, params.truncate || 4000) : undefined;
|
66
61
|
|
67
62
|
const getImage = async () => {
|
@@ -82,7 +77,7 @@ export async function generateOrAssignContentType(payload: DSLActivityExecutionP
|
|
82
77
|
|
83
78
|
const fileRef = await getImage();
|
84
79
|
|
85
|
-
log.info("Execute SelectDocumentType interaction on content with \nexisting types: " + existing_types.join(","));
|
80
|
+
log.info("Execute SelectDocumentType interaction on content with \nexisting types: " + existing_types.map(t => t.name).join(","));
|
86
81
|
|
87
82
|
const res = await executeInteractionFromActivity(client, interactionName, params, {
|
88
83
|
existing_types, content, image: fileRef
|
@@ -98,7 +93,6 @@ export async function generateOrAssignContentType(payload: DSLActivityExecutionP
|
|
98
93
|
if (!selectedType) {
|
99
94
|
log.warn("Document type not idenfified: starting type generation");
|
100
95
|
const newType = await generateNewType(context, existing_types, content, fileRef);
|
101
|
-
|
102
96
|
selectedType = { id: newType.id, name: newType.name };
|
103
97
|
}
|
104
98
|
|
@@ -119,14 +113,14 @@ export async function generateOrAssignContentType(payload: DSLActivityExecutionP
|
|
119
113
|
};
|
120
114
|
}
|
121
115
|
|
122
|
-
async function generateNewType(context: ActivityContext<GenerateOrAssignContentTypeParams>, existing_types:
|
116
|
+
async function generateNewType(context: ActivityContext<GenerateOrAssignContentTypeParams>, existing_types: ContentObjectTypeItem[], content?: string, fileRef?: string) {
|
123
117
|
const { client, params } = context;
|
124
118
|
|
125
119
|
const project = await context.fetchProject();
|
126
120
|
const interactionName = params.interactionNames?.generateMetadataModel ?? INT_GENERATE_METADATA_MODEL;
|
127
121
|
|
128
122
|
const genTypeRes = await executeInteractionFromActivity(client, interactionName, params, {
|
129
|
-
existing_types: existing_types,
|
123
|
+
existing_types: existing_types.map(t => t.name),
|
130
124
|
content: content,
|
131
125
|
human_context: project?.configuration?.human_context ?? undefined,
|
132
126
|
image: fileRef ? fileRef : undefined
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import { DSLActivityExecutionPayload, DSLActivitySpec } from "@vertesia/common";
|
2
1
|
import { log } from "@temporalio/activity";
|
2
|
+
import { DSLActivityExecutionPayload, DSLActivitySpec } from "@vertesia/common";
|
3
3
|
import { setupActivity } from "../dsl/setup/ActivityContext.js";
|
4
4
|
import { WorkflowParamNotFound } from "../errors.js";
|
5
5
|
|
6
|
-
interface NotifyWebhookParams {
|
6
|
+
export interface NotifyWebhookParams {
|
7
7
|
target_url: string; //URL to send the notification to
|
8
8
|
method: 'GET' | 'POST'; //HTTP method to use
|
9
9
|
payload: Record<string, any>; //payload to send (if POST then as JSON body, if GET then as query string)
|