@aj-archipelago/cortex 1.3.55 → 1.3.57

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 (44) hide show
  1. package/.env.sample +3 -1
  2. package/config/default.example.json +2 -2
  3. package/config.js +32 -0
  4. package/helper-apps/mogrt-handler/.env.example +24 -0
  5. package/helper-apps/mogrt-handler/README.md +166 -0
  6. package/helper-apps/mogrt-handler/glossaryHandler.js +218 -0
  7. package/helper-apps/mogrt-handler/index.js +213 -0
  8. package/helper-apps/mogrt-handler/package-lock.json +7106 -0
  9. package/helper-apps/mogrt-handler/package.json +34 -0
  10. package/helper-apps/mogrt-handler/s3Handler.js +444 -0
  11. package/helper-apps/mogrt-handler/start.js +98 -0
  12. package/helper-apps/mogrt-handler/swagger.js +42 -0
  13. package/helper-apps/mogrt-handler/swagger.yaml +436 -0
  14. package/helper-apps/mogrt-handler/tests/integration/api.test.js +226 -0
  15. package/helper-apps/mogrt-handler/tests/integration/glossary.test.js +106 -0
  16. package/helper-apps/mogrt-handler/tests/setup.js +8 -0
  17. package/helper-apps/mogrt-handler/tests/test-files/test.gif +1 -0
  18. package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +1 -0
  19. package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +1 -0
  20. package/helper-apps/mogrt-handler/tests/unit/glossary.unit.test.js +118 -0
  21. package/helper-apps/mogrt-handler/tests/unit/index.test.js +349 -0
  22. package/helper-apps/mogrt-handler/tests/unit/s3Handler.test.js +204 -0
  23. package/helper-apps/mogrt-handler/tests/unit/sample.test.js +28 -0
  24. package/helper-apps/mogrt-handler/vitest.config.js +15 -0
  25. package/lib/entityConstants.js +1 -1
  26. package/lib/requestExecutor.js +1 -1
  27. package/package.json +1 -1
  28. package/pathways/list_translation_models.js +67 -0
  29. package/pathways/system/sys_test_response_reasonableness.js +20 -0
  30. package/pathways/system/workspaces/workspace_applet_edit.js +187 -0
  31. package/pathways/translate_apptek.js +11 -0
  32. package/pathways/translate_google.js +10 -0
  33. package/pathways/translate_groq.js +36 -0
  34. package/pathways/video_seedance.js +17 -0
  35. package/pathways/video_veo.js +31 -0
  36. package/server/modelExecutor.js +16 -0
  37. package/server/plugins/apptekTranslatePlugin.js +189 -0
  38. package/server/plugins/googleTranslatePlugin.js +121 -0
  39. package/server/plugins/groqChatPlugin.js +108 -0
  40. package/server/plugins/replicateApiPlugin.js +22 -0
  41. package/server/plugins/veoVideoPlugin.js +218 -0
  42. package/tests/apptekTranslatePlugin.test.js +228 -0
  43. package/tests/integration/apptekTranslatePlugin.integration.test.js +156 -0
  44. package/tests/translate_apptek.test.js +117 -0
@@ -29,6 +29,10 @@ import ReplicateApiPlugin from './plugins/replicateApiPlugin.js';
29
29
  import AzureVideoTranslatePlugin from './plugins/azureVideoTranslatePlugin.js';
30
30
  import OllamaChatPlugin from './plugins/ollamaChatPlugin.js';
31
31
  import OllamaCompletionPlugin from './plugins/ollamaCompletionPlugin.js';
32
+ import ApptekTranslatePlugin from './plugins/apptekTranslatePlugin.js';
33
+ import GoogleTranslatePlugin from './plugins/googleTranslatePlugin.js';
34
+ import GroqChatPlugin from './plugins/groqChatPlugin.js';
35
+ import VeoVideoPlugin from './plugins/veoVideoPlugin.js';
32
36
 
33
37
  class ModelExecutor {
34
38
  constructor(pathway, model) {
@@ -117,6 +121,18 @@ class ModelExecutor {
117
121
  case 'OLLAMA-COMPLETION':
118
122
  plugin = new OllamaCompletionPlugin(pathway, model);
119
123
  break;
124
+ case 'APPTEK-TRANSLATE':
125
+ plugin = new ApptekTranslatePlugin(pathway, model);
126
+ break;
127
+ case 'GOOGLE-TRANSLATE':
128
+ plugin = new GoogleTranslatePlugin(pathway, model);
129
+ break;
130
+ case 'GROQ-CHAT':
131
+ plugin = new GroqChatPlugin(pathway, model);
132
+ break;
133
+ case 'VEO-VIDEO':
134
+ plugin = new VeoVideoPlugin(pathway, model);
135
+ break;
120
136
  default:
121
137
  throw new Error(`Unsupported model type: ${model.type}`);
122
138
  }
@@ -0,0 +1,189 @@
1
+ // ApptekTranslatePlugin.js
2
+ import ModelPlugin from './modelPlugin.js';
3
+ import logger from '../../lib/logger.js';
4
+ const { callPathway } = await import('../../lib/pathwayTools.js');
5
+
6
+
7
+ class ApptekTranslatePlugin extends ModelPlugin {
8
+ constructor(pathway, model) {
9
+ super(pathway, model);
10
+
11
+ // Get API configuration from environment variables through the base class
12
+ const apiEndpoint = this.environmentVariables.APPTEK_API_ENDPOINT;
13
+ const apiKey = this.environmentVariables.APPTEK_API_KEY;
14
+
15
+ if (!apiEndpoint || !apiKey) {
16
+ throw new Error('AppTek API configuration missing. Please check APPTEK_API_ENDPOINT and APPTEK_API_KEY environment variables.');
17
+ }
18
+
19
+ this.apiEndpoint = apiEndpoint;
20
+ this.apiKey = apiKey;
21
+ }
22
+
23
+ // Set up parameters specific to the AppTek Translate API
24
+ getRequestParameters(text, parameters, prompt) {
25
+ const combinedParameters = { ...this.promptParameters, ...parameters };
26
+ const { modelPromptText } = this.getCompiledPrompt(text, parameters, prompt);
27
+
28
+ // For AppTek, we don't need to wrap the text in an object since it expects raw text
29
+ return {
30
+ data: modelPromptText,
31
+ params: {
32
+ from: combinedParameters.from || 'auto',
33
+ to: combinedParameters.to,
34
+ glossaryId: combinedParameters.glossaryId || 'none'
35
+ }
36
+ };
37
+ }
38
+
39
+ // Execute the request to the AppTek Translate API
40
+ async execute(text, parameters, prompt, cortexRequest) {
41
+ try {
42
+ const requestParameters = this.getRequestParameters(text, parameters, prompt);
43
+ const { from = 'auto', to } = requestParameters.params;
44
+
45
+ let sourceLanguage = from;
46
+
47
+ // If source language is 'auto', detect it
48
+ if (from === 'auto') {
49
+ // Assuming requestParameters.data contains the text for language detection (usually same as 'text')
50
+ const detectedLang = await this.detectLanguage(requestParameters.data);
51
+ if (detectedLang) {
52
+ sourceLanguage = detectedLang;
53
+ requestParameters.params.from = detectedLang; // Update for subsequent use
54
+ } else {
55
+ const warnMsg = `ApptekTranslatePlugin: Language detection for 'auto' did not return a language. Proceeding with 'auto' or default.`;
56
+ logger.warn(warnMsg)
57
+ // sourceLanguage remains 'auto'. The comparison 'auto' === to will likely be false.
58
+ }
59
+ }
60
+ // At this point, sourceLanguage is either the initially provided 'from' language,
61
+ // or the detected language if 'from' was 'auto' and detection was successful.
62
+ // requestParameters.params.from is also updated if detection occurred.
63
+
64
+ // Check if source and target languages are the same
65
+ // Ensure 'to' is a valid language string, not empty or null.
66
+ if (to && sourceLanguage && sourceLanguage !== 'auto' && sourceLanguage === to) {
67
+ const logMessage = `ApptekTranslatePlugin: Source language (${sourceLanguage}) matches target language (${to}). Skipping translation.`;
68
+ logger.verbose(logMessage)
69
+ // Return the original text. Ensure the return format matches what `this.executeRequest`
70
+ // would return for a successful translation (e.g., string or object).
71
+ // Assuming it's a string based on typical translation plugin behavior.
72
+ return text;
73
+ }
74
+
75
+ // Construct the URL with the (potentially detected) source language and target language
76
+ const langPair = `${requestParameters.params.from}-${to}`; // requestParameters.params.from is correctly set
77
+ cortexRequest.url = `${this.apiEndpoint}/api/v2/quicktranslate/${langPair}`;
78
+ cortexRequest.data = requestParameters.data;
79
+ cortexRequest.method = 'POST';
80
+ cortexRequest.headers = {
81
+ 'x-token': this.apiKey,
82
+ 'Accept': 'application/json',
83
+ 'Content-Type': 'text/plain'
84
+ };
85
+
86
+ // Add glossary_id parameter if it's provided and not 'none'
87
+ if (requestParameters.params.glossaryId && requestParameters.params.glossaryId !== 'none') {
88
+ const url = new URL(cortexRequest.url);
89
+ url.searchParams.append('glossary_id', requestParameters.params.glossaryId);
90
+ cortexRequest.url = url.toString();
91
+
92
+ const glossaryLogMessage = `ApptekTranslatePlugin: Using glossary ID: ${requestParameters.params.glossaryId}`;
93
+ logger.verbose(glossaryLogMessage)
94
+ }
95
+
96
+ return this.executeRequest(cortexRequest);
97
+ } catch (error) {
98
+ // If AppTek translation fails, use Groq as a fallback
99
+ logger.warn(`AppTek translation failed: ${error.message}. Falling back to Groq translate.`);
100
+
101
+ try {
102
+ // Import the callPathway function if it hasn't been imported at the top
103
+ const { callPathway } = await import('../../lib/pathwayTools.js');
104
+
105
+ // Call the Groq translate pathway as a fallback
106
+ const result = await callPathway('translate_groq', {
107
+ text,
108
+ to: parameters.to || this.promptParameters.to,
109
+ });
110
+
111
+ logger.verbose('Successfully used Groq translate as fallback');
112
+ return result;
113
+ } catch (fallbackError) {
114
+ // If even the fallback fails, log it and rethrow the original error
115
+ logger.error(`Groq translate fallback also failed: ${fallbackError.message}`);
116
+ throw fallbackError;
117
+ }
118
+ }
119
+ }
120
+
121
+ // Detect language using AppTek's language detection API
122
+ async detectLanguage(text) {
123
+ try {
124
+ // Make language detection request
125
+ const resultResponse = await fetch(`${this.apiEndpoint}/api/v2/quick_lid`, {
126
+ method: 'POST',
127
+ headers: {
128
+ 'x-token': this.apiKey,
129
+ 'Accept': 'application/json',
130
+ 'Content-Type': 'text/plain'
131
+ },
132
+ body: text
133
+ });
134
+
135
+ let detectedLanguage = null;
136
+
137
+
138
+ if (resultResponse.status === 200) {
139
+ const result = await resultResponse.text();
140
+ detectedLanguage = result.split('\n')[0].split(';')[0];
141
+ }else {
142
+ logger.error(`Apptek Language detection failed with status: ${resultResponse.status}`);
143
+ logger.debug({error: resultResponse, text})
144
+ }
145
+
146
+ if (!detectedLanguage) {
147
+ throw new Error('Language detection failed');
148
+ }
149
+
150
+ return detectedLanguage;
151
+
152
+ } catch (error) {
153
+ try {
154
+ // Call the language pathway as a fallback
155
+ const detectedLanguage = await callPathway('language', {
156
+ text,
157
+ });
158
+
159
+ logger.verbose('Successfully used language pathway as fallback', {detectedLanguage});
160
+ if (!detectedLanguage) {
161
+ throw new Error('Language detection failed using fallback language pathway');
162
+ }
163
+ return detectedLanguage;
164
+ } catch (fallbackError) {
165
+ // If even the fallback fails, log it and rethrow the original error
166
+ logger.error(`Language pathway fallback also failed: ${fallbackError.message}`);
167
+ throw fallbackError;
168
+ }
169
+ }
170
+ }
171
+
172
+ // Parse the response from the AppTek Translate API
173
+ parseResponse(data) {
174
+ // AppTek returns the translated text directly
175
+ return data.trim();
176
+ }
177
+
178
+ // Override the logging function to display the request and response
179
+ logRequestData(data, responseData, prompt) {
180
+ logger.verbose(`Input: ${data}`);
181
+ logger.verbose(`Output: ${this.parseResponse(responseData)}`);
182
+
183
+ if (prompt?.debugInfo) {
184
+ prompt.debugInfo += `\nInput: ${data}\nOutput: ${responseData}`;
185
+ }
186
+ }
187
+ }
188
+
189
+ export default ApptekTranslatePlugin;
@@ -0,0 +1,121 @@
1
+ // GoogleTranslatePlugin.js
2
+ import ModelPlugin from './modelPlugin.js';
3
+ import logger from '../../lib/logger.js';
4
+
5
+ class GoogleTranslatePlugin extends ModelPlugin {
6
+ constructor(pathway, model) {
7
+ super(pathway, model);
8
+
9
+ // Get API configuration from environment variables through the base class
10
+ const projectId = this.environmentVariables.GOOGLE_CLOUD_PROJECT_ID;
11
+ const apiKey = this.environmentVariables.GOOGLE_CLOUD_API_KEY;
12
+
13
+ if (!projectId && !apiKey) {
14
+ throw new Error('Google Cloud Translation API configuration missing. Please check GOOGLE_CLOUD_PROJECT_ID or GOOGLE_CLOUD_API_KEY environment variables.');
15
+ }
16
+
17
+ this.projectId = projectId;
18
+ this.apiKey = apiKey;
19
+ this.location = this.environmentVariables.GOOGLE_CLOUD_LOCATION || 'global';
20
+ }
21
+
22
+ // Set up parameters specific to the Google Translate API
23
+ getRequestParameters(text, parameters, prompt) {
24
+ const combinedParameters = { ...this.promptParameters, ...parameters };
25
+ const { modelPromptText } = this.getCompiledPrompt(text, parameters, prompt);
26
+
27
+
28
+ const requestParameters = {
29
+ data: {
30
+ q: [modelPromptText],
31
+ target: combinedParameters.to
32
+ },
33
+ params: {}
34
+ };
35
+
36
+ // Add source language if provided and not 'auto'
37
+ if (combinedParameters.from && combinedParameters.from !== 'auto') {
38
+ requestParameters.data.source = combinedParameters.from;
39
+ }
40
+
41
+ // Configure API version - v2 is simpler for basic translation
42
+ if (this.apiKey) {
43
+ // Using API key authentication (v2 API)
44
+ requestParameters.params.key = this.apiKey;
45
+ } else {
46
+ // Using OAuth with project ID (v3 API)
47
+ requestParameters.data.parent = `projects/${this.projectId}/locations/${this.location}`;
48
+ }
49
+
50
+ return requestParameters;
51
+ }
52
+
53
+ // Execute the request to the Google Translate API
54
+ async execute(text, parameters, prompt, cortexRequest) {
55
+ const requestParameters = this.getRequestParameters(text, parameters, prompt);
56
+
57
+ // Configure the API version based on authentication method
58
+ if (this.apiKey) {
59
+ // Using API key authentication (v2 API)
60
+ cortexRequest.url = 'https://translation.googleapis.com/language/translate/v2';
61
+ cortexRequest.method = 'POST';
62
+ cortexRequest.data = requestParameters.data;
63
+ cortexRequest.params = requestParameters.params;
64
+ cortexRequest.headers = {
65
+ 'Content-Type': 'application/json'
66
+ };
67
+ } else {
68
+ // Using OAuth with project ID (v3 API)
69
+ cortexRequest.url = `https://translation.googleapis.com/v3/projects/${this.projectId}/locations/${this.location}:translateText`;
70
+ cortexRequest.method = 'POST';
71
+ cortexRequest.data = {
72
+ contents: requestParameters.data.q,
73
+ targetLanguageCode: requestParameters.data.target,
74
+ sourceLanguageCode: requestParameters.data.source,
75
+ mimeType: 'text/plain',
76
+ };
77
+ cortexRequest.headers = {
78
+ 'Content-Type': 'application/json',
79
+ 'Authorization': `Bearer ${await this.getAccessToken()}`
80
+ };
81
+ }
82
+
83
+ return this.executeRequest(cortexRequest);
84
+ }
85
+
86
+ // Get access token for OAuth authentication
87
+ async getAccessToken() {
88
+ // This would be implemented if using OAuth authentication
89
+ // For simplicity, we recommend using API key authentication
90
+ return null;
91
+ }
92
+
93
+ // Parse the response from the Google Translate API
94
+ parseResponse(data) {
95
+ // Handle v2 API response
96
+ if (data && data.data && data.data.translations) {
97
+ return data.data.translations[0].translatedText.trim();
98
+ }
99
+ // Handle v3 API response
100
+ else if (data && data.translations) {
101
+ return data.translations[0].translatedText.trim();
102
+ } else {
103
+ return data;
104
+ }
105
+ }
106
+
107
+ // Override the logging function to display the request and response
108
+ logRequestData(data, responseData, prompt) {
109
+ const modelInput = data.q ? data.q[0] : (data.contents ? data.contents[0] : '');
110
+ const translatedText = this.parseResponse(responseData);
111
+
112
+ logger.verbose(`Input: ${modelInput}`);
113
+ logger.verbose(`Output: ${translatedText}`);
114
+
115
+ if (prompt?.debugInfo) {
116
+ prompt.debugInfo += `\nInput: ${modelInput}\nOutput: ${translatedText}`;
117
+ }
118
+ }
119
+ }
120
+
121
+ export default GoogleTranslatePlugin;
@@ -0,0 +1,108 @@
1
+ // GroqChatPlugin.js
2
+ import ModelPlugin from './modelPlugin.js';
3
+ import logger from '../../lib/logger.js';
4
+
5
+ class GroqChatPlugin extends ModelPlugin {
6
+ constructor(pathway, model) {
7
+ super(pathway, model);
8
+ }
9
+
10
+ // Set up parameters specific to the Groq API
11
+ getRequestParameters(text, parameters, prompt) {
12
+ const combinedParameters = { ...this.promptParameters, ...parameters };
13
+ const { modelPromptText, modelPromptMessages, tokenLength, modelPrompt } = this.getCompiledPrompt(text, parameters, prompt);
14
+
15
+ // Use modelPromptMessages if available, otherwise fall back to other formats
16
+ let messages = [];
17
+
18
+ if (modelPromptMessages && Array.isArray(modelPromptMessages) && modelPromptMessages.length > 0) {
19
+ // Use the messages directly from modelPromptMessages
20
+ messages = modelPromptMessages;
21
+ } else if (modelPrompt && modelPrompt.messages) {
22
+ // If the prompt is already in a messages format, use it directly
23
+ messages = modelPrompt.messages;
24
+ } else if (typeof modelPromptText === 'string') {
25
+ // If it's a string, convert to a user message
26
+ messages = [
27
+ { role: 'user', content: modelPromptText }
28
+ ];
29
+
30
+ // If a system message is provided in parameters, prepend it
31
+ if (combinedParameters.systemMessage) {
32
+ messages.unshift({
33
+ role: 'system',
34
+ content: combinedParameters.systemMessage
35
+ });
36
+ }
37
+ }
38
+
39
+ // Build request parameters for Groq API
40
+ const requestParameters = {
41
+ data: {
42
+ model: this.model.params?.model || "meta-llama/llama-4-scout-17b-16e-instruct", // Default model if not specified
43
+ messages: messages,
44
+ temperature: combinedParameters.temperature !== undefined ? combinedParameters.temperature : 0.7,
45
+ max_completion_tokens: combinedParameters.max_tokens || 4096,
46
+ top_p: combinedParameters.top_p !== undefined ? combinedParameters.top_p : 1,
47
+ stream: combinedParameters.stream === true
48
+ }
49
+ };
50
+
51
+ // Add optional parameters if they exist
52
+ if (combinedParameters.stop) {
53
+ requestParameters.data.stop = combinedParameters.stop;
54
+ }
55
+
56
+ if (combinedParameters.presence_penalty !== undefined) {
57
+ requestParameters.data.presence_penalty = combinedParameters.presence_penalty;
58
+ }
59
+
60
+ if (combinedParameters.frequency_penalty !== undefined) {
61
+ requestParameters.data.frequency_penalty = combinedParameters.frequency_penalty;
62
+ }
63
+
64
+ return requestParameters;
65
+ }
66
+
67
+ // Execute the request to the Groq API
68
+ async execute(text, parameters, prompt, cortexRequest) {
69
+ const requestParameters = this.getRequestParameters(text, parameters, prompt);
70
+
71
+ // Configure the request for Groq API
72
+ cortexRequest.url = this.model.url;
73
+ cortexRequest.method = 'POST';
74
+ cortexRequest.data = requestParameters.data;
75
+ cortexRequest.headers = this.model.headers;
76
+
77
+ return this.executeRequest(cortexRequest);
78
+ }
79
+
80
+ // Parse the response from the Groq API
81
+ parseResponse(data) {
82
+ if (data && data.choices && data.choices.length > 0) {
83
+ if (data.choices[0].message && data.choices[0].message.content) {
84
+ return data.choices[0].message.content.trim();
85
+ } else if (data.choices[0].text) {
86
+ return data.choices[0].text.trim();
87
+ }
88
+ }
89
+ return data;
90
+ }
91
+
92
+ // Override the logging function to display the request and response
93
+ logRequestData(data, responseData, prompt) {
94
+ // Find the user message
95
+ const userMessage = data.messages.find(msg => msg.role === 'user');
96
+ const modelInput = userMessage ? userMessage.content : JSON.stringify(data.messages);
97
+ const modelOutput = this.parseResponse(responseData);
98
+
99
+ logger.verbose(`Input: ${modelInput}`);
100
+ logger.verbose(`Output: ${modelOutput}`);
101
+
102
+ if (prompt?.debugInfo) {
103
+ prompt.debugInfo += `\nInput: ${modelInput}\nOutput: ${modelOutput}`;
104
+ }
105
+ }
106
+ }
107
+
108
+ export default GroqChatPlugin;
@@ -35,6 +35,7 @@ class ReplicateApiPlugin extends ModelPlugin {
35
35
  height: combinedParameters.height,
36
36
  size: combinedParameters.size || "1024x1024",
37
37
  style: combinedParameters.style || "realistic_image",
38
+ ...(combinedParameters.seed && Number.isInteger(combinedParameters.seed) ? { seed: combinedParameters.seed } : {}),
38
39
  },
39
40
  };
40
41
  break;
@@ -109,6 +110,7 @@ class ReplicateApiPlugin extends ModelPlugin {
109
110
  input_image: combinedParameters.input_image,
110
111
  aspect_ratio: validRatios.includes(combinedParameters.aspectRatio) ? combinedParameters.aspectRatio : "1:1",
111
112
  safety_tolerance: safetyTolerance,
113
+ ...(combinedParameters.seed && Number.isInteger(combinedParameters.seed && combinedParameters.seed > 0) ? { seed: combinedParameters.seed } : {}),
112
114
  },
113
115
  };
114
116
  break;
@@ -131,6 +133,26 @@ class ReplicateApiPlugin extends ModelPlugin {
131
133
  input_image_2: combinedParameters.input_image_2,
132
134
  aspect_ratio: validRatios.includes(combinedParameters.aspectRatio) ? combinedParameters.aspectRatio : "1:1",
133
135
  safety_tolerance: safetyTolerance,
136
+ ...(combinedParameters.seed && Number.isInteger(combinedParameters.seed && combinedParameters.seed > 0) ? { seed: combinedParameters.seed } : {}),
137
+ },
138
+ };
139
+ break;
140
+ }
141
+ case "replicate-seedance-1-pro": {
142
+ const validResolutions = ["480p", "1080p"];
143
+ const validRatios = ["16:9", "4:3", "9:16", "1:1", "3:4", "21:9", "9:21"];
144
+ const validFps = [24];
145
+
146
+ requestParameters = {
147
+ input: {
148
+ prompt: modelPromptText,
149
+ resolution: validResolutions.includes(combinedParameters.resolution) ? combinedParameters.resolution : "1080p",
150
+ aspect_ratio: validRatios.includes(combinedParameters.aspectRatio) ? combinedParameters.aspectRatio : "16:9",
151
+ ...(combinedParameters.seed && Number.isInteger(combinedParameters.seed && combinedParameters.seed > 0) ? { seed: combinedParameters.seed } : {}),
152
+ fps: validFps.includes(combinedParameters.fps) ? combinedParameters.fps : 24,
153
+ camera_fixed: combinedParameters.camera_fixed || false,
154
+ duration: combinedParameters.duration || 5,
155
+ ...(combinedParameters.image ? { image: combinedParameters.image } : {}),
134
156
  },
135
157
  };
136
158
  break;