@aj-archipelago/cortex 1.3.67 โ 1.4.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/config.js +27 -0
- package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/__init__.py +3 -0
- package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/function.json +20 -0
- package/helper-apps/cortex-doc-to-pdf/Dockerfile +46 -0
- package/helper-apps/cortex-doc-to-pdf/README.md +408 -0
- package/helper-apps/cortex-doc-to-pdf/converter.py +157 -0
- package/helper-apps/cortex-doc-to-pdf/docker-compose.yml +23 -0
- package/helper-apps/cortex-doc-to-pdf/document_converter.py +181 -0
- package/helper-apps/cortex-doc-to-pdf/examples/README.md +252 -0
- package/helper-apps/cortex-doc-to-pdf/examples/nodejs-client.js +266 -0
- package/helper-apps/cortex-doc-to-pdf/examples/package-lock.json +297 -0
- package/helper-apps/cortex-doc-to-pdf/examples/package.json +23 -0
- package/helper-apps/cortex-doc-to-pdf/function_app.py +85 -0
- package/helper-apps/cortex-doc-to-pdf/host.json +16 -0
- package/helper-apps/cortex-doc-to-pdf/request_handlers.py +193 -0
- package/helper-apps/cortex-doc-to-pdf/requirements.txt +3 -0
- package/helper-apps/cortex-doc-to-pdf/tests/run_tests.sh +26 -0
- package/helper-apps/cortex-doc-to-pdf/tests/test_conversion.py +320 -0
- package/helper-apps/cortex-doc-to-pdf/tests/test_streaming.py +419 -0
- package/helper-apps/cortex-file-handler/package-lock.json +1 -0
- package/helper-apps/cortex-file-handler/package.json +1 -0
- package/helper-apps/cortex-file-handler/src/services/ConversionService.js +81 -8
- package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +54 -7
- package/helper-apps/cortex-file-handler/tests/getOperations.test.js +19 -7
- package/lib/encodeCache.js +5 -0
- package/lib/keyValueStorageClient.js +5 -0
- package/lib/logger.js +1 -1
- package/lib/pathwayTools.js +8 -1
- package/lib/redisSubscription.js +6 -0
- package/lib/requestExecutor.js +4 -0
- package/lib/util.js +88 -0
- package/package.json +1 -1
- package/pathways/basePathway.js +3 -3
- package/pathways/bing_afagent.js +1 -0
- package/pathways/gemini_15_vision.js +1 -1
- package/pathways/google_cse.js +2 -2
- package/pathways/image_gemini_25.js +85 -0
- package/pathways/image_prompt_optimizer_gemini_25.js +149 -0
- package/pathways/image_qwen.js +28 -0
- package/pathways/image_seedream4.js +26 -0
- package/pathways/rag.js +1 -1
- package/pathways/rag_jarvis.js +1 -1
- package/pathways/system/entity/sys_entity_continue.js +1 -1
- package/pathways/system/entity/sys_generator_results.js +1 -1
- package/pathways/system/entity/tools/sys_tool_google_search.js +15 -2
- package/pathways/system/entity/tools/sys_tool_grok_x_search.js +3 -3
- package/pathways/system/entity/tools/sys_tool_image.js +28 -23
- package/pathways/system/entity/tools/sys_tool_image_gemini.js +135 -0
- package/server/graphql.js +9 -2
- package/server/modelExecutor.js +4 -0
- package/server/pathwayResolver.js +19 -18
- package/server/plugins/claude3VertexPlugin.js +13 -8
- package/server/plugins/gemini15ChatPlugin.js +15 -10
- package/server/plugins/gemini15VisionPlugin.js +2 -23
- package/server/plugins/gemini25ImagePlugin.js +155 -0
- package/server/plugins/modelPlugin.js +3 -2
- package/server/plugins/openAiChatPlugin.js +6 -6
- package/server/plugins/replicateApiPlugin.js +268 -12
- package/server/plugins/veoVideoPlugin.js +15 -1
- package/server/rest.js +2 -0
- package/server/typeDef.js +96 -10
- package/tests/integration/apptekTranslatePlugin.integration.test.js +1 -1
- package/tests/unit/core/pathwayManager.test.js +2 -4
- package/tests/unit/plugins/gemini25ImagePlugin.test.js +294 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "doc-to-pdf-client-examples",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "doc-to-pdf-client-examples",
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"axios": "^1.6.0",
|
|
13
|
+
"form-data": "^4.0.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {}
|
|
16
|
+
},
|
|
17
|
+
"node_modules/asynckit": {
|
|
18
|
+
"version": "0.4.0",
|
|
19
|
+
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
20
|
+
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
},
|
|
23
|
+
"node_modules/axios": {
|
|
24
|
+
"version": "1.12.2",
|
|
25
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
|
26
|
+
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"follow-redirects": "^1.15.6",
|
|
30
|
+
"form-data": "^4.0.4",
|
|
31
|
+
"proxy-from-env": "^1.1.0"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"node_modules/call-bind-apply-helpers": {
|
|
35
|
+
"version": "1.0.2",
|
|
36
|
+
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
|
37
|
+
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"es-errors": "^1.3.0",
|
|
41
|
+
"function-bind": "^1.1.2"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">= 0.4"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"node_modules/combined-stream": {
|
|
48
|
+
"version": "1.0.8",
|
|
49
|
+
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
|
50
|
+
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"delayed-stream": "~1.0.0"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">= 0.8"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"node_modules/delayed-stream": {
|
|
60
|
+
"version": "1.0.0",
|
|
61
|
+
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
62
|
+
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
|
63
|
+
"license": "MIT",
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=0.4.0"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"node_modules/dunder-proto": {
|
|
69
|
+
"version": "1.0.1",
|
|
70
|
+
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
71
|
+
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
|
72
|
+
"license": "MIT",
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"call-bind-apply-helpers": "^1.0.1",
|
|
75
|
+
"es-errors": "^1.3.0",
|
|
76
|
+
"gopd": "^1.2.0"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">= 0.4"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"node_modules/es-define-property": {
|
|
83
|
+
"version": "1.0.1",
|
|
84
|
+
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
85
|
+
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
|
86
|
+
"license": "MIT",
|
|
87
|
+
"engines": {
|
|
88
|
+
"node": ">= 0.4"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"node_modules/es-errors": {
|
|
92
|
+
"version": "1.3.0",
|
|
93
|
+
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
|
94
|
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
|
95
|
+
"license": "MIT",
|
|
96
|
+
"engines": {
|
|
97
|
+
"node": ">= 0.4"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"node_modules/es-object-atoms": {
|
|
101
|
+
"version": "1.1.1",
|
|
102
|
+
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
|
103
|
+
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
|
104
|
+
"license": "MIT",
|
|
105
|
+
"dependencies": {
|
|
106
|
+
"es-errors": "^1.3.0"
|
|
107
|
+
},
|
|
108
|
+
"engines": {
|
|
109
|
+
"node": ">= 0.4"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"node_modules/es-set-tostringtag": {
|
|
113
|
+
"version": "2.1.0",
|
|
114
|
+
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
|
115
|
+
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
|
116
|
+
"license": "MIT",
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"es-errors": "^1.3.0",
|
|
119
|
+
"get-intrinsic": "^1.2.6",
|
|
120
|
+
"has-tostringtag": "^1.0.2",
|
|
121
|
+
"hasown": "^2.0.2"
|
|
122
|
+
},
|
|
123
|
+
"engines": {
|
|
124
|
+
"node": ">= 0.4"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"node_modules/follow-redirects": {
|
|
128
|
+
"version": "1.15.11",
|
|
129
|
+
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
|
130
|
+
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
|
131
|
+
"funding": [
|
|
132
|
+
{
|
|
133
|
+
"type": "individual",
|
|
134
|
+
"url": "https://github.com/sponsors/RubenVerborgh"
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"license": "MIT",
|
|
138
|
+
"engines": {
|
|
139
|
+
"node": ">=4.0"
|
|
140
|
+
},
|
|
141
|
+
"peerDependenciesMeta": {
|
|
142
|
+
"debug": {
|
|
143
|
+
"optional": true
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"node_modules/form-data": {
|
|
148
|
+
"version": "4.0.4",
|
|
149
|
+
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
|
150
|
+
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
|
151
|
+
"license": "MIT",
|
|
152
|
+
"dependencies": {
|
|
153
|
+
"asynckit": "^0.4.0",
|
|
154
|
+
"combined-stream": "^1.0.8",
|
|
155
|
+
"es-set-tostringtag": "^2.1.0",
|
|
156
|
+
"hasown": "^2.0.2",
|
|
157
|
+
"mime-types": "^2.1.12"
|
|
158
|
+
},
|
|
159
|
+
"engines": {
|
|
160
|
+
"node": ">= 6"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"node_modules/function-bind": {
|
|
164
|
+
"version": "1.1.2",
|
|
165
|
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
|
166
|
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
|
167
|
+
"license": "MIT",
|
|
168
|
+
"funding": {
|
|
169
|
+
"url": "https://github.com/sponsors/ljharb"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"node_modules/get-intrinsic": {
|
|
173
|
+
"version": "1.3.0",
|
|
174
|
+
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
|
175
|
+
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
|
176
|
+
"license": "MIT",
|
|
177
|
+
"dependencies": {
|
|
178
|
+
"call-bind-apply-helpers": "^1.0.2",
|
|
179
|
+
"es-define-property": "^1.0.1",
|
|
180
|
+
"es-errors": "^1.3.0",
|
|
181
|
+
"es-object-atoms": "^1.1.1",
|
|
182
|
+
"function-bind": "^1.1.2",
|
|
183
|
+
"get-proto": "^1.0.1",
|
|
184
|
+
"gopd": "^1.2.0",
|
|
185
|
+
"has-symbols": "^1.1.0",
|
|
186
|
+
"hasown": "^2.0.2",
|
|
187
|
+
"math-intrinsics": "^1.1.0"
|
|
188
|
+
},
|
|
189
|
+
"engines": {
|
|
190
|
+
"node": ">= 0.4"
|
|
191
|
+
},
|
|
192
|
+
"funding": {
|
|
193
|
+
"url": "https://github.com/sponsors/ljharb"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"node_modules/get-proto": {
|
|
197
|
+
"version": "1.0.1",
|
|
198
|
+
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
|
199
|
+
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
|
200
|
+
"license": "MIT",
|
|
201
|
+
"dependencies": {
|
|
202
|
+
"dunder-proto": "^1.0.1",
|
|
203
|
+
"es-object-atoms": "^1.0.0"
|
|
204
|
+
},
|
|
205
|
+
"engines": {
|
|
206
|
+
"node": ">= 0.4"
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"node_modules/gopd": {
|
|
210
|
+
"version": "1.2.0",
|
|
211
|
+
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
|
212
|
+
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
|
213
|
+
"license": "MIT",
|
|
214
|
+
"engines": {
|
|
215
|
+
"node": ">= 0.4"
|
|
216
|
+
},
|
|
217
|
+
"funding": {
|
|
218
|
+
"url": "https://github.com/sponsors/ljharb"
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"node_modules/has-symbols": {
|
|
222
|
+
"version": "1.1.0",
|
|
223
|
+
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
|
224
|
+
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
|
225
|
+
"license": "MIT",
|
|
226
|
+
"engines": {
|
|
227
|
+
"node": ">= 0.4"
|
|
228
|
+
},
|
|
229
|
+
"funding": {
|
|
230
|
+
"url": "https://github.com/sponsors/ljharb"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"node_modules/has-tostringtag": {
|
|
234
|
+
"version": "1.0.2",
|
|
235
|
+
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
|
236
|
+
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
|
237
|
+
"license": "MIT",
|
|
238
|
+
"dependencies": {
|
|
239
|
+
"has-symbols": "^1.0.3"
|
|
240
|
+
},
|
|
241
|
+
"engines": {
|
|
242
|
+
"node": ">= 0.4"
|
|
243
|
+
},
|
|
244
|
+
"funding": {
|
|
245
|
+
"url": "https://github.com/sponsors/ljharb"
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"node_modules/hasown": {
|
|
249
|
+
"version": "2.0.2",
|
|
250
|
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
|
251
|
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
|
252
|
+
"license": "MIT",
|
|
253
|
+
"dependencies": {
|
|
254
|
+
"function-bind": "^1.1.2"
|
|
255
|
+
},
|
|
256
|
+
"engines": {
|
|
257
|
+
"node": ">= 0.4"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"node_modules/math-intrinsics": {
|
|
261
|
+
"version": "1.1.0",
|
|
262
|
+
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
263
|
+
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
|
264
|
+
"license": "MIT",
|
|
265
|
+
"engines": {
|
|
266
|
+
"node": ">= 0.4"
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
"node_modules/mime-db": {
|
|
270
|
+
"version": "1.52.0",
|
|
271
|
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
272
|
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
|
273
|
+
"license": "MIT",
|
|
274
|
+
"engines": {
|
|
275
|
+
"node": ">= 0.6"
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
"node_modules/mime-types": {
|
|
279
|
+
"version": "2.1.35",
|
|
280
|
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
281
|
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
282
|
+
"license": "MIT",
|
|
283
|
+
"dependencies": {
|
|
284
|
+
"mime-db": "1.52.0"
|
|
285
|
+
},
|
|
286
|
+
"engines": {
|
|
287
|
+
"node": ">= 0.6"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"node_modules/proxy-from-env": {
|
|
291
|
+
"version": "1.1.0",
|
|
292
|
+
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
293
|
+
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
|
294
|
+
"license": "MIT"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "doc-to-pdf-client-examples",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Client examples for Document to PDF Converter service",
|
|
5
|
+
"main": "nodejs-client.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"example": "node nodejs-client.js",
|
|
8
|
+
"test": "node nodejs-client.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"axios": "^1.6.0",
|
|
12
|
+
"form-data": "^4.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pdf",
|
|
17
|
+
"converter",
|
|
18
|
+
"document",
|
|
19
|
+
"streaming"
|
|
20
|
+
],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "ISC"
|
|
23
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Main application entry point for Azure Functions and standalone server."""
|
|
2
|
+
|
|
3
|
+
import azure.functions as func
|
|
4
|
+
import logging
|
|
5
|
+
import asyncio
|
|
6
|
+
from aiohttp import web
|
|
7
|
+
import os
|
|
8
|
+
from request_handlers import handle_azure_function_request, handle_aiohttp_request
|
|
9
|
+
|
|
10
|
+
# Azure Functions app
|
|
11
|
+
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.route(route="convert")
|
|
15
|
+
async def http_convert_trigger(req: func.HttpRequest) -> func.HttpResponse:
|
|
16
|
+
"""
|
|
17
|
+
Azure Function HTTP trigger for document to PDF conversion.
|
|
18
|
+
|
|
19
|
+
Note: For GET requests with URLs containing special characters (like Azure SAS tokens),
|
|
20
|
+
the URI should be URL-encoded, or use POST with JSON body instead.
|
|
21
|
+
"""
|
|
22
|
+
return await handle_azure_function_request(req)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ============================================================================
|
|
26
|
+
# Standalone aiohttp server for container deployment
|
|
27
|
+
# ============================================================================
|
|
28
|
+
|
|
29
|
+
async def main_server():
|
|
30
|
+
"""Main function to run the standalone aiohttp server."""
|
|
31
|
+
logging.basicConfig(
|
|
32
|
+
level=logging.INFO,
|
|
33
|
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
port_str = os.environ.get("PORT", "8080")
|
|
37
|
+
try:
|
|
38
|
+
port = int(port_str)
|
|
39
|
+
except ValueError:
|
|
40
|
+
logging.warning(f"Invalid PORT environment variable '{port_str}'. Defaulting to 8080.")
|
|
41
|
+
port = 8080
|
|
42
|
+
|
|
43
|
+
aiohttp_app = web.Application()
|
|
44
|
+
|
|
45
|
+
# Conversion endpoints (both /convert and / root)
|
|
46
|
+
aiohttp_app.router.add_post('/convert', handle_aiohttp_request)
|
|
47
|
+
aiohttp_app.router.add_get('/convert', handle_aiohttp_request)
|
|
48
|
+
aiohttp_app.router.add_post('/', handle_aiohttp_request) # Root path also works
|
|
49
|
+
|
|
50
|
+
# Health check endpoint
|
|
51
|
+
async def health_check(request):
|
|
52
|
+
return web.json_response({"status": "healthy", "service": "doc-to-pdf-converter"})
|
|
53
|
+
|
|
54
|
+
aiohttp_app.router.add_get('/health', health_check)
|
|
55
|
+
aiohttp_app.router.add_get('/', health_check) # GET / returns health check
|
|
56
|
+
|
|
57
|
+
runner = web.AppRunner(aiohttp_app)
|
|
58
|
+
await runner.setup()
|
|
59
|
+
site = web.TCPSite(runner, '0.0.0.0', port)
|
|
60
|
+
await site.start()
|
|
61
|
+
|
|
62
|
+
logging.info(f"๐ Document to PDF Converter server started on http://0.0.0.0:{port}")
|
|
63
|
+
print(f"======== Running on http://0.0.0.0:{port} ========")
|
|
64
|
+
print(f"======== Convert: POST http://0.0.0.0:{port}/ or /convert ========")
|
|
65
|
+
print(f"======== Health: GET http://0.0.0.0:{port}/health ========")
|
|
66
|
+
print("(Press CTRL+C to quit)")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
while True:
|
|
70
|
+
await asyncio.sleep(3600)
|
|
71
|
+
except KeyboardInterrupt:
|
|
72
|
+
logging.info("Server shutting down...")
|
|
73
|
+
finally:
|
|
74
|
+
await runner.cleanup()
|
|
75
|
+
logging.info("Server stopped.")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
"""Run as standalone server when executed directly."""
|
|
80
|
+
try:
|
|
81
|
+
asyncio.run(main_server())
|
|
82
|
+
except KeyboardInterrupt:
|
|
83
|
+
logging.info("Application shut down by user.")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logging.critical(f"Application failed to start or crashed: {e}")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0",
|
|
3
|
+
"logging": {
|
|
4
|
+
"applicationInsights": {
|
|
5
|
+
"samplingSettings": {
|
|
6
|
+
"isEnabled": true,
|
|
7
|
+
"maxTelemetryItemsPerSecond": 20
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"extensionBundle": {
|
|
12
|
+
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
|
13
|
+
"version": "[4.*, 5.0.0)"
|
|
14
|
+
},
|
|
15
|
+
"functionTimeout": "00:10:00"
|
|
16
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""HTTP request handlers for both Azure Functions and standalone server."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import json
|
|
5
|
+
import urllib.parse
|
|
6
|
+
from aiohttp import web
|
|
7
|
+
import azure.functions as func
|
|
8
|
+
from document_converter import convert_from_uri, convert_from_stream
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def handle_azure_function_request(req: func.HttpRequest) -> func.HttpResponse:
|
|
12
|
+
"""
|
|
13
|
+
Azure Function HTTP trigger handler.
|
|
14
|
+
|
|
15
|
+
Supports GET and POST methods.
|
|
16
|
+
Accepts 'uri' parameter (query string or JSON body) or file upload.
|
|
17
|
+
Returns PDF as binary data with appropriate headers.
|
|
18
|
+
"""
|
|
19
|
+
logging.info('Azure Function triggered')
|
|
20
|
+
|
|
21
|
+
# Get URI parameter from query string or request body
|
|
22
|
+
uri = req.params.get('uri')
|
|
23
|
+
if not uri:
|
|
24
|
+
try:
|
|
25
|
+
req_body = await req.get_json()
|
|
26
|
+
uri = req_body.get('uri')
|
|
27
|
+
except ValueError:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
if not uri:
|
|
31
|
+
return func.HttpResponse(
|
|
32
|
+
json.dumps({
|
|
33
|
+
"error": "Missing 'uri' parameter",
|
|
34
|
+
"message": "Please provide a 'uri' parameter with the document URL to convert. For URLs with special characters (like SAS tokens), use POST with JSON body or URL-encode the URI parameter."
|
|
35
|
+
}),
|
|
36
|
+
status_code=400,
|
|
37
|
+
mimetype="application/json"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# URL decode if it looks encoded (contains %XX patterns)
|
|
41
|
+
if '%' in uri:
|
|
42
|
+
try:
|
|
43
|
+
uri = urllib.parse.unquote(uri)
|
|
44
|
+
logging.info(f"URL-decoded URI to: {uri[:100]}...")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logging.warning(f"Failed to URL-decode URI: {e}")
|
|
47
|
+
|
|
48
|
+
# Process the conversion
|
|
49
|
+
result = await convert_from_uri(uri)
|
|
50
|
+
|
|
51
|
+
if result.get("success"):
|
|
52
|
+
# Return PDF as binary data
|
|
53
|
+
return func.HttpResponse(
|
|
54
|
+
body=result["data"],
|
|
55
|
+
status_code=200,
|
|
56
|
+
mimetype="application/pdf",
|
|
57
|
+
headers={
|
|
58
|
+
"Content-Disposition": f'attachment; filename="{result["filename"]}"',
|
|
59
|
+
"Content-Type": "application/pdf"
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
# Return error as JSON
|
|
64
|
+
error_response = {k: v for k, v in result.items() if k != "success"}
|
|
65
|
+
return func.HttpResponse(
|
|
66
|
+
json.dumps(error_response),
|
|
67
|
+
status_code=400 if "Unsupported" in str(result.get("error")) or "download" in str(result.get("error")) else 500,
|
|
68
|
+
mimetype="application/json"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def handle_aiohttp_request(request: web.Request) -> web.Response:
|
|
73
|
+
"""
|
|
74
|
+
aiohttp standalone server request handler.
|
|
75
|
+
|
|
76
|
+
Supports file upload (multipart/form-data) and URI-based conversion.
|
|
77
|
+
Returns streaming PDF response to avoid RAM bloat.
|
|
78
|
+
"""
|
|
79
|
+
logging.info(f'Request received. Content-Type: {request.content_type}')
|
|
80
|
+
|
|
81
|
+
result = None
|
|
82
|
+
|
|
83
|
+
# Check if this is a multipart file upload (streaming upload)
|
|
84
|
+
if request.content_type and 'multipart/form-data' in request.content_type:
|
|
85
|
+
logging.info("Processing multipart file upload")
|
|
86
|
+
try:
|
|
87
|
+
reader = await request.multipart()
|
|
88
|
+
file_data = None
|
|
89
|
+
filename = None
|
|
90
|
+
|
|
91
|
+
# Stream the file data
|
|
92
|
+
async for part in reader:
|
|
93
|
+
if part.name == 'file':
|
|
94
|
+
filename = part.filename or 'document'
|
|
95
|
+
# Read file in chunks to avoid RAM bloat
|
|
96
|
+
chunks = []
|
|
97
|
+
chunk_count = 0
|
|
98
|
+
while True:
|
|
99
|
+
chunk = await part.read_chunk(8192) # 8KB chunks
|
|
100
|
+
if not chunk:
|
|
101
|
+
break
|
|
102
|
+
chunks.append(chunk)
|
|
103
|
+
chunk_count += 1
|
|
104
|
+
|
|
105
|
+
file_data = b''.join(chunks)
|
|
106
|
+
logging.info(f"Received file: {filename}, size: {len(file_data)} bytes ({chunk_count} chunks)")
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if not file_data:
|
|
110
|
+
return web.json_response(
|
|
111
|
+
{"error": "No file provided in multipart upload"},
|
|
112
|
+
status=400
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Convert from uploaded file
|
|
116
|
+
result = await convert_from_stream(file_data, filename)
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logging.error(f"Error processing multipart upload: {e}", exc_info=True)
|
|
120
|
+
return web.json_response(
|
|
121
|
+
{"error": "Failed to process file upload", "details": str(e)},
|
|
122
|
+
status=400
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
# Try URI-based conversion
|
|
126
|
+
uri = None
|
|
127
|
+
|
|
128
|
+
# Try to get URI from query parameters
|
|
129
|
+
uri = request.query.get('uri')
|
|
130
|
+
if uri:
|
|
131
|
+
logging.info(f"Found URI in query parameters: {uri[:100]}...")
|
|
132
|
+
# URL decode if it looks encoded
|
|
133
|
+
if '%' in uri:
|
|
134
|
+
try:
|
|
135
|
+
decoded_uri = urllib.parse.unquote(uri)
|
|
136
|
+
logging.info(f"URL-decoded URI to: {decoded_uri[:100]}...")
|
|
137
|
+
uri = decoded_uri
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logging.warning(f"Failed to URL-decode URI: {e}")
|
|
140
|
+
else:
|
|
141
|
+
# Try to get from JSON body
|
|
142
|
+
logging.info("URI not found in query parameters. Checking JSON body.")
|
|
143
|
+
try:
|
|
144
|
+
req_body = await request.json()
|
|
145
|
+
if req_body:
|
|
146
|
+
uri = req_body.get('uri')
|
|
147
|
+
if uri:
|
|
148
|
+
logging.info(f"Found URI in JSON body: {uri[:100]}...")
|
|
149
|
+
except json.JSONDecodeError:
|
|
150
|
+
logging.info("Request body is not valid JSON.")
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logging.warning(f"Error reading JSON body: {e}")
|
|
153
|
+
|
|
154
|
+
if not uri:
|
|
155
|
+
logging.warning("Neither file upload nor URI provided")
|
|
156
|
+
return web.json_response(
|
|
157
|
+
{
|
|
158
|
+
"error": "Please provide either a file upload or a 'uri' parameter",
|
|
159
|
+
"supported_methods": {
|
|
160
|
+
"file_upload": "POST with multipart/form-data and 'file' field",
|
|
161
|
+
"uri": "POST with JSON body containing 'uri' field, or GET with 'uri' query parameter"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
status=400
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Validate URI format
|
|
168
|
+
if not uri.startswith(('http://', 'https://')):
|
|
169
|
+
logging.error(f"Invalid URI format: {uri}")
|
|
170
|
+
return web.json_response(
|
|
171
|
+
{"error": "Invalid URI format. Must start with http:// or https://"},
|
|
172
|
+
status=400
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Process the conversion from URI
|
|
176
|
+
result = await convert_from_uri(uri)
|
|
177
|
+
|
|
178
|
+
# Return streaming response
|
|
179
|
+
if result.get("success"):
|
|
180
|
+
# Stream PDF response to avoid loading entire file in RAM
|
|
181
|
+
return web.Response(
|
|
182
|
+
body=result["data"],
|
|
183
|
+
status=200,
|
|
184
|
+
content_type="application/pdf",
|
|
185
|
+
headers={
|
|
186
|
+
"Content-Disposition": f'attachment; filename="{result["filename"]}"'
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
# Return error as JSON
|
|
191
|
+
error_response = {k: v for k, v in result.items() if k != "success"}
|
|
192
|
+
status_code = 400 if "Unsupported" in str(result.get("error")) or "download" in str(result.get("error")) else 500
|
|
193
|
+
return web.json_response(error_response, status=status_code)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Run end-to-end conversion tests
|
|
3
|
+
|
|
4
|
+
echo "๐งช Running Document to PDF Conversion Tests"
|
|
5
|
+
echo "============================================="
|
|
6
|
+
echo ""
|
|
7
|
+
|
|
8
|
+
# Check if LibreOffice is installed
|
|
9
|
+
if ! command -v soffice &> /dev/null && ! command -v libreoffice &> /dev/null; then
|
|
10
|
+
echo "โ Error: LibreOffice not found!"
|
|
11
|
+
echo "Please install LibreOffice:"
|
|
12
|
+
echo " - macOS: brew install --cask libreoffice"
|
|
13
|
+
echo " - Ubuntu/Debian: sudo apt-get install libreoffice"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Check if Python requirements are installed
|
|
18
|
+
if ! python3 -c "import PyPDF2" 2>/dev/null; then
|
|
19
|
+
echo "๐ฆ Installing Python dependencies..."
|
|
20
|
+
pip3 install -r requirements.txt
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Run tests
|
|
24
|
+
python3 test_conversion.py
|
|
25
|
+
|
|
26
|
+
exit $?
|