@igoruehara/canvas-flow 0.1.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.
Files changed (283) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/bin/canvas-flow.js +1132 -0
  4. package/package.json +68 -0
  5. package/public/assets/index-PCQkqMUe.css +1 -0
  6. package/public/assets/index-qV8twxcq.js +767 -0
  7. package/public/index.html +13 -0
  8. package/server/api-key/api-key-connect-provider.d.ts +104 -0
  9. package/server/api-key/api-key-connect-provider.js +14 -0
  10. package/server/api-key/api-key-connect-provider.js.map +1 -0
  11. package/server/api-key/api-key-constants-model.d.ts +2 -0
  12. package/server/api-key/api-key-constants-model.js +6 -0
  13. package/server/api-key/api-key-constants-model.js.map +1 -0
  14. package/server/api-key/api-key-controller.d.ts +12 -0
  15. package/server/api-key/api-key-controller.js +86 -0
  16. package/server/api-key/api-key-controller.js.map +1 -0
  17. package/server/api-key/api-key-module.d.ts +2 -0
  18. package/server/api-key/api-key-module.js +27 -0
  19. package/server/api-key/api-key-module.js.map +1 -0
  20. package/server/api-key/api-key-schema.d.ts +72 -0
  21. package/server/api-key/api-key-schema.js +98 -0
  22. package/server/api-key/api-key-schema.js.map +1 -0
  23. package/server/api-key/api-key-service.d.ts +45 -0
  24. package/server/api-key/api-key-service.js +151 -0
  25. package/server/api-key/api-key-service.js.map +1 -0
  26. package/server/api-key/dto/create-api-key.dto.d.ts +8 -0
  27. package/server/api-key/dto/create-api-key.dto.js +7 -0
  28. package/server/api-key/dto/create-api-key.dto.js.map +1 -0
  29. package/server/app.module.d.ts +2 -0
  30. package/server/app.module.js +53 -0
  31. package/server/app.module.js.map +1 -0
  32. package/server/auth/auth-connect-provider.d.ts +140 -0
  33. package/server/auth/auth-connect-provider.js +20 -0
  34. package/server/auth/auth-connect-provider.js.map +1 -0
  35. package/server/auth/auth-constants-model.d.ts +4 -0
  36. package/server/auth/auth-constants-model.js +8 -0
  37. package/server/auth/auth-constants-model.js.map +1 -0
  38. package/server/auth/auth-controller.d.ts +25 -0
  39. package/server/auth/auth-controller.js +96 -0
  40. package/server/auth/auth-controller.js.map +1 -0
  41. package/server/auth/auth-module.d.ts +2 -0
  42. package/server/auth/auth-module.js +26 -0
  43. package/server/auth/auth-module.js.map +1 -0
  44. package/server/auth/auth-organization-schema.d.ts +44 -0
  45. package/server/auth/auth-organization-schema.js +62 -0
  46. package/server/auth/auth-organization-schema.js.map +1 -0
  47. package/server/auth/auth-schema.d.ts +56 -0
  48. package/server/auth/auth-schema.js +77 -0
  49. package/server/auth/auth-schema.js.map +1 -0
  50. package/server/auth/auth-service.d.ts +64 -0
  51. package/server/auth/auth-service.js +343 -0
  52. package/server/auth/auth-service.js.map +1 -0
  53. package/server/canvas-flow/canvas-flow-connect-provider.d.ts +278 -0
  54. package/server/canvas-flow/canvas-flow-connect-provider.js +24 -0
  55. package/server/canvas-flow/canvas-flow-connect-provider.js.map +1 -0
  56. package/server/canvas-flow/canvas-flow-constants-model.d.ts +6 -0
  57. package/server/canvas-flow/canvas-flow-constants-model.js +10 -0
  58. package/server/canvas-flow/canvas-flow-constants-model.js.map +1 -0
  59. package/server/canvas-flow/canvas-flow-controller.d.ts +98 -0
  60. package/server/canvas-flow/canvas-flow-controller.js +423 -0
  61. package/server/canvas-flow/canvas-flow-controller.js.map +1 -0
  62. package/server/canvas-flow/canvas-flow-module.d.ts +2 -0
  63. package/server/canvas-flow/canvas-flow-module.js +27 -0
  64. package/server/canvas-flow/canvas-flow-module.js.map +1 -0
  65. package/server/canvas-flow/canvas-flow-schema.d.ts +192 -0
  66. package/server/canvas-flow/canvas-flow-schema.js +239 -0
  67. package/server/canvas-flow/canvas-flow-schema.js.map +1 -0
  68. package/server/canvas-flow/canvas-flow-service.d.ts +250 -0
  69. package/server/canvas-flow/canvas-flow-service.js +1681 -0
  70. package/server/canvas-flow/canvas-flow-service.js.map +1 -0
  71. package/server/canvas-flow/dto/create-canvas-flow.dto.d.ts +11 -0
  72. package/server/canvas-flow/dto/create-canvas-flow.dto.js +61 -0
  73. package/server/canvas-flow/dto/create-canvas-flow.dto.js.map +1 -0
  74. package/server/canvas-flow/dto/update-canvas-flow.dto.d.ts +10 -0
  75. package/server/canvas-flow/dto/update-canvas-flow.dto.js +56 -0
  76. package/server/canvas-flow/dto/update-canvas-flow.dto.js.map +1 -0
  77. package/server/constants-global.d.ts +1 -0
  78. package/server/constants-global.js +5 -0
  79. package/server/constants-global.js.map +1 -0
  80. package/server/database/database.module.d.ts +2 -0
  81. package/server/database/database.module.js +23 -0
  82. package/server/database/database.module.js.map +1 -0
  83. package/server/database/database.providers.d.ts +7 -0
  84. package/server/database/database.providers.js +26 -0
  85. package/server/database/database.providers.js.map +1 -0
  86. package/server/documents/documents-connect-provider.d.ts +140 -0
  87. package/server/documents/documents-connect-provider.js +14 -0
  88. package/server/documents/documents-connect-provider.js.map +1 -0
  89. package/server/documents/documents-constants-model.d.ts +2 -0
  90. package/server/documents/documents-constants-model.js +6 -0
  91. package/server/documents/documents-constants-model.js.map +1 -0
  92. package/server/documents/documents-controller.d.ts +16 -0
  93. package/server/documents/documents-controller.js +117 -0
  94. package/server/documents/documents-controller.js.map +1 -0
  95. package/server/documents/documents-module.d.ts +2 -0
  96. package/server/documents/documents-module.js +27 -0
  97. package/server/documents/documents-module.js.map +1 -0
  98. package/server/documents/documents-schema.d.ts +96 -0
  99. package/server/documents/documents-schema.js +38 -0
  100. package/server/documents/documents-schema.js.map +1 -0
  101. package/server/documents/documents-service.d.ts +164 -0
  102. package/server/documents/documents-service.js +1417 -0
  103. package/server/documents/documents-service.js.map +1 -0
  104. package/server/flow-tag/flow-tag-connect-provider.d.ts +146 -0
  105. package/server/flow-tag/flow-tag-connect-provider.js +14 -0
  106. package/server/flow-tag/flow-tag-connect-provider.js.map +1 -0
  107. package/server/flow-tag/flow-tag-constants-model.d.ts +2 -0
  108. package/server/flow-tag/flow-tag-constants-model.js +6 -0
  109. package/server/flow-tag/flow-tag-constants-model.js.map +1 -0
  110. package/server/flow-tag/flow-tag-module.d.ts +2 -0
  111. package/server/flow-tag/flow-tag-module.js +24 -0
  112. package/server/flow-tag/flow-tag-module.js.map +1 -0
  113. package/server/flow-tag/flow-tag-schema.d.ts +100 -0
  114. package/server/flow-tag/flow-tag-schema.js +131 -0
  115. package/server/flow-tag/flow-tag-schema.js.map +1 -0
  116. package/server/flow-tag/flow-tag-service.d.ts +77 -0
  117. package/server/flow-tag/flow-tag-service.js +156 -0
  118. package/server/flow-tag/flow-tag-service.js.map +1 -0
  119. package/server/health.controller.d.ts +7 -0
  120. package/server/health.controller.js +33 -0
  121. package/server/health.controller.js.map +1 -0
  122. package/server/http-batch/http-batch-controller.d.ts +345 -0
  123. package/server/http-batch/http-batch-controller.js +40 -0
  124. package/server/http-batch/http-batch-controller.js.map +1 -0
  125. package/server/http-batch/http-batch-module.d.ts +2 -0
  126. package/server/http-batch/http-batch-module.js +25 -0
  127. package/server/http-batch/http-batch-module.js.map +1 -0
  128. package/server/http-batch/http-batch-service.d.ts +381 -0
  129. package/server/http-batch/http-batch-service.js +268 -0
  130. package/server/http-batch/http-batch-service.js.map +1 -0
  131. package/server/lambda.d.ts +2 -0
  132. package/server/lambda.js +115 -0
  133. package/server/lambda.js.map +1 -0
  134. package/server/llm/openai-provider.d.ts +8 -0
  135. package/server/llm/openai-provider.js +256 -0
  136. package/server/llm/openai-provider.js.map +1 -0
  137. package/server/main.d.ts +1 -0
  138. package/server/main.js +80 -0
  139. package/server/main.js.map +1 -0
  140. package/server/mcp-oauth/mcp-oauth-connect-provider.d.ts +164 -0
  141. package/server/mcp-oauth/mcp-oauth-connect-provider.js +14 -0
  142. package/server/mcp-oauth/mcp-oauth-connect-provider.js.map +1 -0
  143. package/server/mcp-oauth/mcp-oauth-constants-model.d.ts +2 -0
  144. package/server/mcp-oauth/mcp-oauth-constants-model.js +6 -0
  145. package/server/mcp-oauth/mcp-oauth-constants-model.js.map +1 -0
  146. package/server/mcp-oauth/mcp-oauth-controller.d.ts +66 -0
  147. package/server/mcp-oauth/mcp-oauth-controller.js +166 -0
  148. package/server/mcp-oauth/mcp-oauth-controller.js.map +1 -0
  149. package/server/mcp-oauth/mcp-oauth-module.d.ts +2 -0
  150. package/server/mcp-oauth/mcp-oauth-module.js +27 -0
  151. package/server/mcp-oauth/mcp-oauth-module.js.map +1 -0
  152. package/server/mcp-oauth/mcp-oauth-schema.d.ts +112 -0
  153. package/server/mcp-oauth/mcp-oauth-schema.js +148 -0
  154. package/server/mcp-oauth/mcp-oauth-schema.js.map +1 -0
  155. package/server/mcp-oauth/mcp-oauth-service.d.ts +189 -0
  156. package/server/mcp-oauth/mcp-oauth-service.js +545 -0
  157. package/server/mcp-oauth/mcp-oauth-service.js.map +1 -0
  158. package/server/memory/memory-connect-provider.d.ts +200 -0
  159. package/server/memory/memory-connect-provider.js +26 -0
  160. package/server/memory/memory-connect-provider.js.map +1 -0
  161. package/server/memory/memory-constants-model.d.ts +6 -0
  162. package/server/memory/memory-constants-model.js +10 -0
  163. package/server/memory/memory-constants-model.js.map +1 -0
  164. package/server/memory/memory-controller.d.ts +15 -0
  165. package/server/memory/memory-controller.js +53 -0
  166. package/server/memory/memory-controller.js.map +1 -0
  167. package/server/memory/memory-history-schema.d.ts +48 -0
  168. package/server/memory/memory-history-schema.js +62 -0
  169. package/server/memory/memory-history-schema.js.map +1 -0
  170. package/server/memory/memory-module.d.ts +2 -0
  171. package/server/memory/memory-module.js +26 -0
  172. package/server/memory/memory-module.js.map +1 -0
  173. package/server/memory/memory-schema.d.ts +48 -0
  174. package/server/memory/memory-schema.js +62 -0
  175. package/server/memory/memory-schema.js.map +1 -0
  176. package/server/memory/memory-service.d.ts +134 -0
  177. package/server/memory/memory-service.js +317 -0
  178. package/server/memory/memory-service.js.map +1 -0
  179. package/server/memory/memory-trace-history-schema.d.ts +48 -0
  180. package/server/memory/memory-trace-history-schema.js +62 -0
  181. package/server/memory/memory-trace-history-schema.js.map +1 -0
  182. package/server/observability/observability.d.ts +3 -0
  183. package/server/observability/observability.js +62 -0
  184. package/server/observability/observability.js.map +1 -0
  185. package/server/production-guard.d.ts +9 -0
  186. package/server/production-guard.js +105 -0
  187. package/server/production-guard.js.map +1 -0
  188. package/server/provider-config/provider-config-connect-provider.d.ts +44 -0
  189. package/server/provider-config/provider-config-connect-provider.js +14 -0
  190. package/server/provider-config/provider-config-connect-provider.js.map +1 -0
  191. package/server/provider-config/provider-config-constants-model.d.ts +3 -0
  192. package/server/provider-config/provider-config-constants-model.js +7 -0
  193. package/server/provider-config/provider-config-constants-model.js.map +1 -0
  194. package/server/provider-config/provider-config-controller.d.ts +23 -0
  195. package/server/provider-config/provider-config-controller.js +80 -0
  196. package/server/provider-config/provider-config-controller.js.map +1 -0
  197. package/server/provider-config/provider-config-module.d.ts +2 -0
  198. package/server/provider-config/provider-config-module.js +27 -0
  199. package/server/provider-config/provider-config-module.js.map +1 -0
  200. package/server/provider-config/provider-config-schema.d.ts +32 -0
  201. package/server/provider-config/provider-config-schema.js +46 -0
  202. package/server/provider-config/provider-config-schema.js.map +1 -0
  203. package/server/provider-config/provider-config-service.d.ts +178 -0
  204. package/server/provider-config/provider-config-service.js +689 -0
  205. package/server/provider-config/provider-config-service.js.map +1 -0
  206. package/server/queue/queue-job-connect-provider.d.ts +128 -0
  207. package/server/queue/queue-job-connect-provider.js +14 -0
  208. package/server/queue/queue-job-connect-provider.js.map +1 -0
  209. package/server/queue/queue-job-constants-model.d.ts +2 -0
  210. package/server/queue/queue-job-constants-model.js +6 -0
  211. package/server/queue/queue-job-constants-model.js.map +1 -0
  212. package/server/queue/queue-job-schema.d.ts +88 -0
  213. package/server/queue/queue-job-schema.js +119 -0
  214. package/server/queue/queue-job-schema.js.map +1 -0
  215. package/server/queue/queue-lock-connect-provider.d.ts +44 -0
  216. package/server/queue/queue-lock-connect-provider.js +14 -0
  217. package/server/queue/queue-lock-connect-provider.js.map +1 -0
  218. package/server/queue/queue-lock-constants-model.d.ts +2 -0
  219. package/server/queue/queue-lock-constants-model.js +6 -0
  220. package/server/queue/queue-lock-constants-model.js.map +1 -0
  221. package/server/queue/queue-lock-schema.d.ts +32 -0
  222. package/server/queue/queue-lock-schema.js +47 -0
  223. package/server/queue/queue-lock-schema.js.map +1 -0
  224. package/server/queue/queue-message-dedupe-connect-provider.d.ts +116 -0
  225. package/server/queue/queue-message-dedupe-connect-provider.js +14 -0
  226. package/server/queue/queue-message-dedupe-connect-provider.js.map +1 -0
  227. package/server/queue/queue-message-dedupe-constants-model.d.ts +2 -0
  228. package/server/queue/queue-message-dedupe-constants-model.js +6 -0
  229. package/server/queue/queue-message-dedupe-constants-model.js.map +1 -0
  230. package/server/queue/queue-message-dedupe-schema.d.ts +80 -0
  231. package/server/queue/queue-message-dedupe-schema.js +108 -0
  232. package/server/queue/queue-message-dedupe-schema.js.map +1 -0
  233. package/server/queue/queue-module.d.ts +2 -0
  234. package/server/queue/queue-module.js +33 -0
  235. package/server/queue/queue-module.js.map +1 -0
  236. package/server/queue/queue-rate-limit-connect-provider.d.ts +56 -0
  237. package/server/queue/queue-rate-limit-connect-provider.js +14 -0
  238. package/server/queue/queue-rate-limit-connect-provider.js.map +1 -0
  239. package/server/queue/queue-rate-limit-constants-model.d.ts +2 -0
  240. package/server/queue/queue-rate-limit-constants-model.js +6 -0
  241. package/server/queue/queue-rate-limit-constants-model.js.map +1 -0
  242. package/server/queue/queue-rate-limit-schema.d.ts +40 -0
  243. package/server/queue/queue-rate-limit-schema.js +57 -0
  244. package/server/queue/queue-rate-limit-schema.js.map +1 -0
  245. package/server/queue/sqs-transition-service.d.ts +123 -0
  246. package/server/queue/sqs-transition-service.js +442 -0
  247. package/server/queue/sqs-transition-service.js.map +1 -0
  248. package/server/rag/rag-controller.d.ts +167 -0
  249. package/server/rag/rag-controller.js +232 -0
  250. package/server/rag/rag-controller.js.map +1 -0
  251. package/server/rag/rag-module.d.ts +2 -0
  252. package/server/rag/rag-module.js +30 -0
  253. package/server/rag/rag-module.js.map +1 -0
  254. package/server/rag/rag-service.d.ts +361 -0
  255. package/server/rag/rag-service.js +2864 -0
  256. package/server/rag/rag-service.js.map +1 -0
  257. package/server/runner/flow-templates.d.ts +55 -0
  258. package/server/runner/flow-templates.js +388 -0
  259. package/server/runner/flow-templates.js.map +1 -0
  260. package/server/runner/langgraph-runtime.service.d.ts +77 -0
  261. package/server/runner/langgraph-runtime.service.js +221 -0
  262. package/server/runner/langgraph-runtime.service.js.map +1 -0
  263. package/server/runner/runner-controller.d.ts +1044 -0
  264. package/server/runner/runner-controller.js +751 -0
  265. package/server/runner/runner-controller.js.map +1 -0
  266. package/server/runner/runner-module.d.ts +2 -0
  267. package/server/runner/runner-module.js +37 -0
  268. package/server/runner/runner-module.js.map +1 -0
  269. package/server/runner/runner-queue-processor.d.ts +29 -0
  270. package/server/runner/runner-queue-processor.js +259 -0
  271. package/server/runner/runner-queue-processor.js.map +1 -0
  272. package/server/runner/runner-service.d.ts +1761 -0
  273. package/server/runner/runner-service.js +14256 -0
  274. package/server/runner/runner-service.js.map +1 -0
  275. package/server/scripts/migrate-canvas-flow-versions.d.ts +1 -0
  276. package/server/scripts/migrate-canvas-flow-versions.js +72 -0
  277. package/server/scripts/migrate-canvas-flow-versions.js.map +1 -0
  278. package/server/scripts/migrate-mcp-oauth-user-scope.d.ts +1 -0
  279. package/server/scripts/migrate-mcp-oauth-user-scope.js +95 -0
  280. package/server/scripts/migrate-mcp-oauth-user-scope.js.map +1 -0
  281. package/templates/config.example.json +204 -0
  282. package/templates/config.production.example.json +206 -0
  283. package/templates/docker-compose.yml +60 -0
@@ -0,0 +1,1417 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DocumentsService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const config_1 = require("@nestjs/config");
18
+ const client_s3_1 = require("@aws-sdk/client-s3");
19
+ const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
20
+ const crypto_1 = require("crypto");
21
+ const fs_1 = require("fs");
22
+ const path_1 = require("path");
23
+ const mongoose_1 = require("mongoose");
24
+ const docx_1 = require("docx");
25
+ const ExcelJS = require("exceljs");
26
+ const PDFDocument = require("pdfkit");
27
+ const PizZip = require("pizzip");
28
+ const Docxtemplater = require("docxtemplater");
29
+ const documents_constants_model_1 = require("./documents-constants-model");
30
+ let DocumentsService = class DocumentsService {
31
+ constructor(model, configService) {
32
+ this.model = model;
33
+ this.configService = configService;
34
+ }
35
+ onModuleInit() {
36
+ void this.model.createIndexes().catch(() => undefined);
37
+ }
38
+ storageMode() {
39
+ const configured = String(this.configService.get('CANVAS_FLOW_FILES_STORAGE') || '').trim().toLowerCase();
40
+ if (configured === 's3')
41
+ return 's3';
42
+ if (configured === 'local')
43
+ return 'local';
44
+ return this.s3Bucket() ? 's3' : 'local';
45
+ }
46
+ s3Bucket() {
47
+ return String(this.configService.get('CANVAS_FLOW_FILES_S3_BUCKET') || '').trim();
48
+ }
49
+ s3Region() {
50
+ return String(this.configService.get('CANVAS_FLOW_FILES_S3_REGION') ||
51
+ this.configService.get('AWS_REGION') ||
52
+ 'us-east-1').trim();
53
+ }
54
+ getS3Client() {
55
+ if (!this.s3Client)
56
+ this.s3Client = new client_s3_1.S3Client({ region: this.s3Region() });
57
+ return this.s3Client;
58
+ }
59
+ localBaseDir() {
60
+ return (0, path_1.resolve)(String(this.configService.get('CANVAS_FLOW_FILES_LOCAL_DIR') || (0, path_1.join)(process.cwd(), 'tmp', 'canvas-flow-documents')));
61
+ }
62
+ publicApiUrl() {
63
+ return String(this.configService.get('CANVAS_FLOW_PUBLIC_URL') ||
64
+ this.configService.get('CANVAS_FLOW_API_PUBLIC_URL') ||
65
+ '').replace(/\/+$/, '');
66
+ }
67
+ safeSegment(value, fallback) {
68
+ return String(value || fallback)
69
+ .normalize('NFD')
70
+ .replace(/[\u0300-\u036f]/g, '')
71
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
72
+ .replace(/^-+|-+$/g, '')
73
+ .slice(0, 160) || fallback;
74
+ }
75
+ fileKey(documentId, filename, scope) {
76
+ return [
77
+ 'canvas-flow',
78
+ this.safeSegment(scope.organizationId, 'global'),
79
+ 'documents',
80
+ documentId,
81
+ this.safeSegment((0, path_1.basename)(filename), 'arquivo.bin'),
82
+ ].join('/');
83
+ }
84
+ resolveLocalPath(key) {
85
+ const baseDir = this.localBaseDir();
86
+ const target = (0, path_1.resolve)(baseDir, ...String(key || '').split('/').filter(Boolean));
87
+ if (target !== baseDir && !target.startsWith(`${baseDir}${path_1.sep}`)) {
88
+ throw new common_1.BadRequestException('Caminho de arquivo invalido.');
89
+ }
90
+ return target;
91
+ }
92
+ async writeBytes(storage, key, buffer, mimeType) {
93
+ if (storage === 's3') {
94
+ const bucket = this.s3Bucket();
95
+ if (!bucket)
96
+ throw new common_1.BadRequestException('CANVAS_FLOW_FILES_S3_BUCKET precisa estar configurado para usar S3.');
97
+ const response = await this.getS3Client().send(new client_s3_1.PutObjectCommand({
98
+ Bucket: bucket,
99
+ Key: key,
100
+ Body: buffer,
101
+ ContentType: mimeType || 'application/octet-stream',
102
+ }));
103
+ return { bucket, versionId: response.VersionId || '', etag: response.ETag || '' };
104
+ }
105
+ const localPath = this.resolveLocalPath(key);
106
+ await fs_1.promises.mkdir((0, path_1.resolve)(localPath, '..'), { recursive: true });
107
+ await fs_1.promises.writeFile(localPath, buffer);
108
+ return { bucket: '', versionId: '', etag: '' };
109
+ }
110
+ async streamToBuffer(stream) {
111
+ if (!stream)
112
+ return Buffer.alloc(0);
113
+ if (typeof stream.transformToByteArray === 'function') {
114
+ return Buffer.from(await stream.transformToByteArray());
115
+ }
116
+ const chunks = [];
117
+ for await (const chunk of stream)
118
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
119
+ return Buffer.concat(chunks);
120
+ }
121
+ toPlain(row) {
122
+ const record = typeof row?.toObject === 'function' ? row.toObject() : { ...(row || {}) };
123
+ delete record._id;
124
+ delete record.__v;
125
+ return {
126
+ ...record,
127
+ id: record.documentId,
128
+ downloadPath: `/api/documents/${encodeURIComponent(String(record.documentId || ''))}/download`,
129
+ };
130
+ }
131
+ contentTypeFor(format) {
132
+ return {
133
+ txt: 'text/plain; charset=utf-8',
134
+ md: 'text/markdown; charset=utf-8',
135
+ csv: 'text/csv; charset=utf-8',
136
+ json: 'application/json; charset=utf-8',
137
+ html: 'text/html; charset=utf-8',
138
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
139
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
140
+ pdf: 'application/pdf',
141
+ }[format];
142
+ }
143
+ getReplacementValue(values, path) {
144
+ if (Object.prototype.hasOwnProperty.call(values || {}, path))
145
+ return values[path];
146
+ return String(path || '').split('.').reduce((current, key) => current?.[key], values);
147
+ }
148
+ assertArtifactFormat(value) {
149
+ const format = String(value || 'txt').toLowerCase();
150
+ if (!['txt', 'md', 'csv', 'json', 'html', 'docx', 'xlsx', 'pdf'].includes(format)) {
151
+ throw new common_1.BadRequestException('Formato de artefato invalido. Use txt, md, csv, json, html, docx, xlsx ou pdf.');
152
+ }
153
+ return format;
154
+ }
155
+ fillTextTemplate(text, replacements) {
156
+ return String(text || '').replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (_match, key) => {
157
+ const value = this.getReplacementValue(replacements, String(key || '').trim());
158
+ if (value === undefined || value === null)
159
+ return '';
160
+ return typeof value === 'string' ? value : JSON.stringify(value);
161
+ });
162
+ }
163
+ unwrapGeneratedDocumentContent(content) {
164
+ const text = String(content || '').trim();
165
+ if (!/^\s*\{[\s\S]*"content"\s*:/.test(text))
166
+ return content;
167
+ try {
168
+ const parsed = JSON.parse(text);
169
+ if (parsed
170
+ && typeof parsed === 'object'
171
+ && !Array.isArray(parsed)
172
+ && (parsed.skill || parsed.plan || parsed.replacements || parsed.docxEdits || parsed.xlsxEdits)
173
+ && typeof (parsed.content ?? parsed.text) === 'string') {
174
+ return String(parsed.content ?? parsed.text);
175
+ }
176
+ }
177
+ catch {
178
+ const looseContent = this.extractLooseJsonStringField(text, 'content');
179
+ if (looseContent)
180
+ return looseContent;
181
+ }
182
+ return content;
183
+ }
184
+ extractLooseJsonStringField(text, field) {
185
+ const pattern = new RegExp(`"${field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:\\s*"`);
186
+ const match = pattern.exec(text);
187
+ if (!match)
188
+ return '';
189
+ let cursor = match.index + match[0].length;
190
+ let output = '';
191
+ while (cursor < text.length) {
192
+ const char = text[cursor];
193
+ if (char === '\\') {
194
+ const next = text[cursor + 1];
195
+ if (next === 'n')
196
+ output += '\n';
197
+ else if (next === 'r')
198
+ output += '\r';
199
+ else if (next === 't')
200
+ output += '\t';
201
+ else if (next === '"' || next === '\\' || next === '/')
202
+ output += next;
203
+ else
204
+ output += next || '';
205
+ cursor += 2;
206
+ continue;
207
+ }
208
+ if (char === '"') {
209
+ const rest = text.slice(cursor + 1);
210
+ if (/^\s*(?:,\s*"[\w.-]+"\s*:|\})/.test(rest))
211
+ break;
212
+ }
213
+ output += char;
214
+ cursor += 1;
215
+ }
216
+ return output.trim();
217
+ }
218
+ flattenReplacements(value, prefix = '', output = {}) {
219
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
220
+ if (prefix)
221
+ output[prefix] = value;
222
+ return output;
223
+ }
224
+ Object.entries(value).forEach(([key, item]) => {
225
+ const path = prefix ? `${prefix}.${key}` : key;
226
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
227
+ this.flattenReplacements(item, path, output);
228
+ }
229
+ else {
230
+ output[path] = item;
231
+ }
232
+ });
233
+ return output;
234
+ }
235
+ inlineText(value) {
236
+ return String(value ?? '')
237
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
238
+ .replace(/__([^_]+)__/g, '$1')
239
+ .replace(/`([^`]+)`/g, '$1')
240
+ .trim();
241
+ }
242
+ normalizedText(value) {
243
+ return this.inlineText(value)
244
+ .normalize('NFD')
245
+ .replace(/[\u0300-\u036f]/g, '')
246
+ .toLowerCase();
247
+ }
248
+ markdownTableCells(line) {
249
+ const trimmed = String(line || '').trim();
250
+ if (!trimmed.includes('|'))
251
+ return null;
252
+ const row = trimmed.replace(/^\|/, '').replace(/\|$/, '');
253
+ const cells = row.split('|').map((cell) => this.inlineText(cell.replace(/\\\|/g, '|')));
254
+ return cells.length >= 2 ? cells : null;
255
+ }
256
+ isMarkdownTableSeparator(line) {
257
+ const cells = this.markdownTableCells(line);
258
+ return Boolean(cells?.length && cells.every((cell) => /^:?-{3,}:?$/.test(cell.replace(/\s+/g, ''))));
259
+ }
260
+ parseRichDocumentContent(content) {
261
+ const blocks = [];
262
+ const lines = String(content || '').replace(/\r\n?/g, '\n').split('\n');
263
+ let paragraph = [];
264
+ let codeLines = [];
265
+ let listItem = null;
266
+ let inCode = false;
267
+ let renderedTitle = false;
268
+ const flushParagraph = () => {
269
+ const text = paragraph.join(' ').trim();
270
+ if (text)
271
+ blocks.push({ type: 'paragraph', text });
272
+ paragraph = [];
273
+ };
274
+ const flushCode = () => {
275
+ if (codeLines.length)
276
+ blocks.push({ type: 'code', lines: codeLines });
277
+ codeLines = [];
278
+ };
279
+ const flushListItem = () => {
280
+ if (listItem?.text)
281
+ blocks.push({ type: 'listItem', ...listItem });
282
+ listItem = null;
283
+ };
284
+ for (let index = 0; index < lines.length; index += 1) {
285
+ const line = lines[index];
286
+ const trimmed = line.trim();
287
+ if (/^```/.test(trimmed)) {
288
+ flushParagraph();
289
+ flushListItem();
290
+ if (inCode)
291
+ flushCode();
292
+ inCode = !inCode;
293
+ continue;
294
+ }
295
+ if (inCode) {
296
+ codeLines.push(line);
297
+ continue;
298
+ }
299
+ if (!trimmed) {
300
+ flushParagraph();
301
+ flushListItem();
302
+ continue;
303
+ }
304
+ if (/^[=_-]{5,}$/.test(trimmed)) {
305
+ flushParagraph();
306
+ flushListItem();
307
+ continue;
308
+ }
309
+ const tableHeader = this.markdownTableCells(line);
310
+ if (tableHeader && this.isMarkdownTableSeparator(lines[index + 1] || '')) {
311
+ flushParagraph();
312
+ flushListItem();
313
+ const rows = [tableHeader];
314
+ index += 2;
315
+ while (index < lines.length) {
316
+ if (!lines[index].trim())
317
+ break;
318
+ if (this.isMarkdownTableSeparator(lines[index])) {
319
+ index += 1;
320
+ continue;
321
+ }
322
+ const row = this.markdownTableCells(lines[index]);
323
+ if (!row)
324
+ break;
325
+ rows.push(row);
326
+ index += 1;
327
+ }
328
+ index -= 1;
329
+ blocks.push({ type: 'table', rows });
330
+ continue;
331
+ }
332
+ const markdownHeading = trimmed.match(/^(#{1,6})\s+(.+)$/);
333
+ const numberedHeading = trimmed.match(/^(\d+(?:\.\d+)*)[.)]?\s+(.+)$/);
334
+ const normalized = this.normalizedText(trimmed);
335
+ const isUpperHeading = trimmed.length <= 110
336
+ && /[A-Z]/.test(trimmed)
337
+ && trimmed === trimmed.toLocaleUpperCase('pt-BR')
338
+ && !/[{}[\]]/.test(trimmed);
339
+ const isNamedHeading = /^(observacao importante|resumo executivo|conteudo principal|responsabilidades|criterios de aceite|objetivo|escopo|premissas|riscos|proximos passos|conclusao)$/.test(normalized);
340
+ if (markdownHeading) {
341
+ flushParagraph();
342
+ flushListItem();
343
+ blocks.push({ type: 'heading', level: Math.min(markdownHeading[1].length, 3), text: markdownHeading[2] });
344
+ renderedTitle = true;
345
+ continue;
346
+ }
347
+ if (!renderedTitle) {
348
+ flushParagraph();
349
+ flushListItem();
350
+ blocks.push({ type: 'heading', level: 1, text: trimmed });
351
+ renderedTitle = true;
352
+ continue;
353
+ }
354
+ if (numberedHeading && (numberedHeading[1].includes('.')
355
+ || (numberedHeading[2].length <= 90 && numberedHeading[2] === numberedHeading[2].toLocaleUpperCase('pt-BR')))) {
356
+ flushParagraph();
357
+ flushListItem();
358
+ blocks.push({
359
+ type: 'heading',
360
+ level: numberedHeading[1].includes('.') ? 3 : 2,
361
+ text: `${numberedHeading[1]} ${numberedHeading[2]}`,
362
+ });
363
+ continue;
364
+ }
365
+ if (isUpperHeading) {
366
+ flushParagraph();
367
+ flushListItem();
368
+ blocks.push({ type: 'heading', level: 2, text: trimmed });
369
+ continue;
370
+ }
371
+ if (isNamedHeading) {
372
+ flushParagraph();
373
+ flushListItem();
374
+ blocks.push({ type: 'heading', level: 3, text: trimmed });
375
+ continue;
376
+ }
377
+ const bullet = trimmed.match(/^[-*\u2022]\s+(.+)$/);
378
+ if (bullet) {
379
+ flushParagraph();
380
+ flushListItem();
381
+ listItem = { text: bullet[1], marker: '' };
382
+ continue;
383
+ }
384
+ if (numberedHeading) {
385
+ flushParagraph();
386
+ flushListItem();
387
+ listItem = { text: numberedHeading[2], marker: `${numberedHeading[1]}.` };
388
+ continue;
389
+ }
390
+ const label = trimmed.match(/^([^:]{1,55}):\s*(.*)$/);
391
+ if (label && !/^https?$/i.test(label[1])) {
392
+ flushParagraph();
393
+ flushListItem();
394
+ blocks.push({ type: 'label', label: label[1], value: label[2] });
395
+ continue;
396
+ }
397
+ if (listItem) {
398
+ listItem.text = `${listItem.text} ${trimmed}`;
399
+ continue;
400
+ }
401
+ paragraph.push(trimmed);
402
+ }
403
+ flushParagraph();
404
+ flushListItem();
405
+ flushCode();
406
+ return blocks;
407
+ }
408
+ renderDocxHeading(text, level) {
409
+ const clean = this.inlineText(text);
410
+ if (!clean)
411
+ return [];
412
+ const heading = level <= 1 ? docx_1.HeadingLevel.HEADING_1 : level === 2 ? docx_1.HeadingLevel.HEADING_2 : docx_1.HeadingLevel.HEADING_3;
413
+ return [new docx_1.Paragraph({
414
+ heading,
415
+ spacing: { before: level <= 1 ? 120 : 220, after: level <= 1 ? 180 : 120 },
416
+ children: [new docx_1.TextRun({
417
+ text: clean,
418
+ bold: true,
419
+ color: level <= 2 ? '17365D' : '1F4E79',
420
+ size: level <= 1 ? 32 : level === 2 ? 26 : 22,
421
+ })],
422
+ })];
423
+ }
424
+ renderDocxParagraph(text) {
425
+ const clean = this.inlineText(text);
426
+ if (!clean)
427
+ return [];
428
+ return [new docx_1.Paragraph({
429
+ spacing: { after: 160, line: 276 },
430
+ children: [new docx_1.TextRun({ text: clean, color: '263238', size: 21 })],
431
+ })];
432
+ }
433
+ renderDocxLabel(label, value) {
434
+ const cleanLabel = this.inlineText(label);
435
+ const cleanValue = this.inlineText(value);
436
+ if (!cleanLabel)
437
+ return [];
438
+ return [new docx_1.Paragraph({
439
+ spacing: { after: 70 },
440
+ children: [
441
+ new docx_1.TextRun({ text: `${cleanLabel}: `, bold: true, color: '374151', size: 20 }),
442
+ new docx_1.TextRun({ text: cleanValue, color: '374151', size: 20 }),
443
+ ],
444
+ })];
445
+ }
446
+ renderDocxListItem(text, marker = '') {
447
+ const clean = this.inlineText(text);
448
+ if (!clean)
449
+ return [];
450
+ return [new docx_1.Paragraph({
451
+ indent: { left: 420, hanging: 220 },
452
+ spacing: { after: 80 },
453
+ children: [
454
+ new docx_1.TextRun({ text: `${marker || '\u2022'} `, bold: true, color: '1F6FEB', size: 20 }),
455
+ new docx_1.TextRun({ text: clean, color: '263238', size: 20 }),
456
+ ],
457
+ })];
458
+ }
459
+ renderDocxCodeBlock(lines) {
460
+ const content = lines.join('\n').trimEnd();
461
+ if (!content)
462
+ return [];
463
+ return content.split('\n').map((line) => new docx_1.Paragraph({
464
+ shading: { fill: 'F5F7FA' },
465
+ spacing: { before: 0, after: 0 },
466
+ children: [new docx_1.TextRun({ text: line || ' ', font: 'Courier New', size: 18, color: '263238' })],
467
+ }));
468
+ }
469
+ renderDocxTable(rows) {
470
+ const columnCount = Math.max(2, Math.min(10, ...rows.map((row) => row.length)));
471
+ const normalizedRows = rows.map((row) => Array.from({ length: columnCount }, (_, index) => this.inlineText(row[index] || '')));
472
+ const border = { style: docx_1.BorderStyle.SINGLE, size: 1, color: 'CBD5E1' };
473
+ return [
474
+ new docx_1.Table({
475
+ width: { size: 100, type: docx_1.WidthType.PERCENTAGE },
476
+ alignment: docx_1.AlignmentType.CENTER,
477
+ borders: { top: border, bottom: border, left: border, right: border, insideHorizontal: border, insideVertical: border },
478
+ rows: normalizedRows.map((row, rowIndex) => new docx_1.TableRow({
479
+ tableHeader: rowIndex === 0,
480
+ children: row.map((cell) => new docx_1.TableCell({
481
+ shading: rowIndex === 0 ? { fill: 'EAF2FF' } : undefined,
482
+ margins: { top: 120, bottom: 120, left: 140, right: 140 },
483
+ children: [new docx_1.Paragraph({
484
+ spacing: { after: 0 },
485
+ children: [new docx_1.TextRun({
486
+ text: cell || ' ',
487
+ bold: rowIndex === 0,
488
+ color: rowIndex === 0 ? '17365D' : '263238',
489
+ size: 19,
490
+ })],
491
+ })],
492
+ })),
493
+ })),
494
+ }),
495
+ new docx_1.Paragraph({ spacing: { after: 140 }, children: [new docx_1.TextRun({ text: '' })] }),
496
+ ];
497
+ }
498
+ async renderNewDocx(content) {
499
+ const blocks = this.parseRichDocumentContent(content);
500
+ const children = blocks.flatMap((block) => {
501
+ if (block.type === 'heading')
502
+ return this.renderDocxHeading(block.text, block.level);
503
+ if (block.type === 'label')
504
+ return this.renderDocxLabel(block.label, block.value);
505
+ if (block.type === 'listItem')
506
+ return this.renderDocxListItem(block.text, block.marker);
507
+ if (block.type === 'code')
508
+ return this.renderDocxCodeBlock(block.lines);
509
+ if (block.type === 'table')
510
+ return this.renderDocxTable(block.rows);
511
+ return this.renderDocxParagraph(block.text);
512
+ });
513
+ return await docx_1.Packer.toBuffer(new docx_1.Document({
514
+ sections: [{
515
+ properties: { page: { margin: { top: 900, right: 900, bottom: 900, left: 900 } } },
516
+ children: children.length ? children : [new docx_1.Paragraph({ text: ' ' })],
517
+ }],
518
+ }));
519
+ }
520
+ escapeXml(value) {
521
+ return String(value ?? '')
522
+ .replace(/&/g, '&amp;')
523
+ .replace(/</g, '&lt;')
524
+ .replace(/>/g, '&gt;')
525
+ .replace(/"/g, '&quot;')
526
+ .replace(/'/g, '&apos;');
527
+ }
528
+ wordParagraphXml(text) {
529
+ return String(text ?? '')
530
+ .split(/\r?\n/)
531
+ .map((line) => `<w:p><w:r><w:t xml:space="preserve">${this.escapeXml(line)}</w:t></w:r></w:p>`)
532
+ .join('');
533
+ }
534
+ wordTableCellXml(text, width) {
535
+ return `<w:tc><w:tcPr><w:tcW w:w="${this.escapeXml(width)}" w:type="dxa"/></w:tcPr>${this.wordParagraphXml(text)}</w:tc>`;
536
+ }
537
+ appendDocxTableColumn(documentXml, edit) {
538
+ let tableIndex = -1;
539
+ return documentXml.replace(/<w:tbl(?:\s[^>]*)?>[\s\S]*?<\/w:tbl>/g, (tableXml) => {
540
+ tableIndex += 1;
541
+ const requestedIndex = Math.max(0, Number(edit.tableIndex || 0));
542
+ if (edit.allTables !== true && tableIndex !== requestedIndex)
543
+ return tableXml;
544
+ const widths = Array.from(tableXml.matchAll(/<w:gridCol\b[^>]*\bw:w="([^"]+)"/g)).map((match) => match[1]);
545
+ const width = widths[widths.length - 1] || '2400';
546
+ let rowIndex = -1;
547
+ let updated = tableXml.replace(/<w:tr(?:\s[^>]*)?>[\s\S]*?<\/w:tr>/g, (rowXml) => {
548
+ rowIndex += 1;
549
+ const rowValue = rowIndex === 0
550
+ ? edit.header || ''
551
+ : Array.isArray(edit.values) && edit.values[rowIndex - 1] !== undefined
552
+ ? edit.values[rowIndex - 1]
553
+ : edit.value || '';
554
+ return rowXml.replace(/<\/w:tr>$/, `${this.wordTableCellXml(rowValue, width)}</w:tr>`);
555
+ });
556
+ updated = updated.replace(/(<w:tblGrid\b[^>]*>)([\s\S]*?)(<\/w:tblGrid>)/, `$1$2<w:gridCol w:w="${this.escapeXml(width)}"/>$3`);
557
+ return updated;
558
+ });
559
+ }
560
+ appendDocxParagraph(documentXml, text) {
561
+ if (!String(text || '').trim())
562
+ return documentXml;
563
+ const paragraph = this.wordParagraphXml(text);
564
+ if (/<w:sectPr\b/.test(documentXml)) {
565
+ return documentXml.replace(/<w:sectPr\b/, `${paragraph}<w:sectPr`);
566
+ }
567
+ return documentXml.replace(/<\/w:body>/, `${paragraph}</w:body>`);
568
+ }
569
+ applyDocxEdits(zip, edits) {
570
+ if (!edits.length)
571
+ return zip;
572
+ const documentFile = zip.file('word/document.xml');
573
+ if (!documentFile)
574
+ throw new common_1.BadRequestException('DOCX sem word/document.xml.');
575
+ let documentXml = documentFile.asText();
576
+ edits.slice(0, 100).forEach((edit) => {
577
+ if (edit?.type === 'append_table_column') {
578
+ documentXml = this.appendDocxTableColumn(documentXml, edit);
579
+ }
580
+ if (edit?.type === 'append_paragraph') {
581
+ documentXml = this.appendDocxParagraph(documentXml, String(edit.text || ''));
582
+ }
583
+ });
584
+ zip.file('word/document.xml', documentXml);
585
+ return zip;
586
+ }
587
+ hasDocxPlaceholders(zip) {
588
+ const documentXml = zip.file('word/document.xml')?.asText() || '';
589
+ const flattenedText = documentXml.replace(/<[^>]+>/g, '');
590
+ return /\{\{\s*[^{}]+?\s*\}\}/.test(flattenedText);
591
+ }
592
+ async renderTemplateDocx(templateDocumentId, replacements, scope, edits = [], fallbackContent = '') {
593
+ const { buffer } = await this.getFile(templateDocumentId, scope);
594
+ try {
595
+ let zip = new PizZip(buffer);
596
+ if (this.hasDocxPlaceholders(zip)) {
597
+ const template = new Docxtemplater(zip, {
598
+ paragraphLoop: true,
599
+ linebreaks: true,
600
+ delimiters: { start: '{{', end: '}}' },
601
+ });
602
+ template.render({ ...(replacements || {}), ...this.flattenReplacements(replacements || {}) });
603
+ zip = template.getZip();
604
+ }
605
+ const effectiveEdits = edits.length || !String(fallbackContent || '').trim()
606
+ ? edits
607
+ : [{ type: 'append_paragraph', text: fallbackContent }];
608
+ return this.applyDocxEdits(zip, effectiveEdits).generate({ type: 'nodebuffer', compression: 'DEFLATE' });
609
+ }
610
+ catch (error) {
611
+ throw new common_1.BadRequestException(`Nao foi possivel preencher o template DOCX: ${error?.message || String(error)}`);
612
+ }
613
+ }
614
+ parseDelimitedLine(line, delimiter) {
615
+ const values = [];
616
+ let current = '';
617
+ let quoted = false;
618
+ for (let index = 0; index < line.length; index += 1) {
619
+ const char = line[index];
620
+ if (char === '"') {
621
+ if (quoted && line[index + 1] === '"') {
622
+ current += '"';
623
+ index += 1;
624
+ }
625
+ else {
626
+ quoted = !quoted;
627
+ }
628
+ continue;
629
+ }
630
+ if (char === delimiter && !quoted) {
631
+ values.push(current.trim());
632
+ current = '';
633
+ continue;
634
+ }
635
+ current += char;
636
+ }
637
+ values.push(current.trim());
638
+ return values;
639
+ }
640
+ parseDelimitedRows(content) {
641
+ const lines = String(content || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
642
+ if (lines.length < 2)
643
+ return null;
644
+ const firstLine = lines.find((line) => !line.startsWith('#')) || lines[0];
645
+ const delimiters = [',', ';', '\t'];
646
+ const delimiter = delimiters
647
+ .map((item) => ({ item, count: (firstLine.match(new RegExp(item === '\t' ? '\\t' : `\\${item}`, 'g')) || []).length }))
648
+ .sort((left, right) => right.count - left.count)[0];
649
+ if (!delimiter || delimiter.count <= 0)
650
+ return null;
651
+ const rows = lines.map((line) => this.parseDelimitedLine(line, delimiter.item)).filter((row) => row.length > 1);
652
+ if (rows.length < 2 || rows.length < Math.max(2, Math.floor(lines.length * 0.6)))
653
+ return null;
654
+ return rows;
655
+ }
656
+ rowsFromJsonContent(content) {
657
+ try {
658
+ const parsed = JSON.parse(String(content || '').trim());
659
+ if (Array.isArray(parsed) && parsed.length) {
660
+ if (parsed.every((item) => item && typeof item === 'object' && !Array.isArray(item))) {
661
+ const headers = Array.from(new Set(parsed.flatMap((item) => Object.keys(item))));
662
+ return [headers, ...parsed.map((item) => headers.map((header) => {
663
+ const value = item[header];
664
+ return typeof value === 'string' ? value : JSON.stringify(value ?? '');
665
+ }))];
666
+ }
667
+ return [['valor'], ...parsed.map((item) => [typeof item === 'string' ? item : JSON.stringify(item ?? '')])];
668
+ }
669
+ if (parsed && typeof parsed === 'object') {
670
+ return [['Campo', 'Valor'], ...Object.entries(parsed).map(([key, value]) => [
671
+ key,
672
+ typeof value === 'string' ? value : JSON.stringify(value ?? ''),
673
+ ])];
674
+ }
675
+ }
676
+ catch {
677
+ return null;
678
+ }
679
+ return null;
680
+ }
681
+ structuredRowsForContent(content) {
682
+ const blocks = this.parseRichDocumentContent(content);
683
+ const firstTable = blocks.find((block) => block.type === 'table');
684
+ return firstTable?.rows || this.rowsFromJsonContent(content) || this.parseDelimitedRows(content);
685
+ }
686
+ xlsxCellBorder() {
687
+ return {
688
+ top: { style: 'thin', color: { argb: 'CBD5E1' } },
689
+ left: { style: 'thin', color: { argb: 'CBD5E1' } },
690
+ bottom: { style: 'thin', color: { argb: 'CBD5E1' } },
691
+ right: { style: 'thin', color: { argb: 'CBD5E1' } },
692
+ };
693
+ }
694
+ normalizeXlsxRows(rows) {
695
+ const columnCount = Math.max(1, Math.min(50, ...rows.map((row) => row.length)));
696
+ return rows.slice(0, 5000).map((row) => (Array.from({ length: columnCount }, (_, index) => this.inlineText(row[index] || ''))));
697
+ }
698
+ writeXlsxTable(worksheet, rows, startRow, title = '') {
699
+ const normalizedRows = this.normalizeXlsxRows(rows);
700
+ if (!normalizedRows.length)
701
+ return startRow;
702
+ const columnCount = normalizedRows[0].length;
703
+ let rowNumber = startRow;
704
+ if (title) {
705
+ worksheet.mergeCells(rowNumber, 1, rowNumber, Math.max(columnCount, 4));
706
+ const titleCell = worksheet.getCell(rowNumber, 1);
707
+ titleCell.value = title;
708
+ titleCell.font = { bold: true, size: 14, color: { argb: '17365D' } };
709
+ titleCell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'EAF2FF' } };
710
+ titleCell.alignment = { vertical: 'middle' };
711
+ worksheet.getRow(rowNumber).height = 24;
712
+ rowNumber += 1;
713
+ }
714
+ const headerRowNumber = rowNumber;
715
+ normalizedRows.forEach((row, rowIndex) => {
716
+ const sheetRow = worksheet.getRow(rowNumber + rowIndex);
717
+ row.forEach((value, columnIndex) => {
718
+ const cell = sheetRow.getCell(columnIndex + 1);
719
+ cell.value = value;
720
+ cell.border = this.xlsxCellBorder();
721
+ cell.alignment = { vertical: 'top', wrapText: true };
722
+ if (rowIndex === 0) {
723
+ cell.font = { bold: true, color: { argb: '17365D' } };
724
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'DCEBFF' } };
725
+ }
726
+ else if (rowIndex % 2 === 0) {
727
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'F8FAFC' } };
728
+ }
729
+ });
730
+ sheetRow.commit();
731
+ });
732
+ if (!worksheet.autoFilter) {
733
+ worksheet.autoFilter = {
734
+ from: { row: headerRowNumber, column: 1 },
735
+ to: { row: headerRowNumber, column: columnCount },
736
+ };
737
+ worksheet.views = [{ state: 'frozen', ySplit: headerRowNumber }];
738
+ }
739
+ return rowNumber + normalizedRows.length + 2;
740
+ }
741
+ writeXlsxMergedRow(worksheet, rowNumber, text, style = 'body') {
742
+ const clean = this.inlineText(text);
743
+ if (!clean)
744
+ return rowNumber;
745
+ const span = 6;
746
+ worksheet.mergeCells(rowNumber, 1, rowNumber, span);
747
+ const cell = worksheet.getCell(rowNumber, 1);
748
+ cell.value = clean;
749
+ cell.alignment = { vertical: 'top', wrapText: true };
750
+ if (style === 'title') {
751
+ cell.font = { bold: true, size: 18, color: { argb: '17365D' } };
752
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'EAF2FF' } };
753
+ worksheet.getRow(rowNumber).height = 30;
754
+ }
755
+ else if (style === 'heading') {
756
+ cell.font = { bold: true, size: 13, color: { argb: '1F4E79' } };
757
+ cell.border = { bottom: { style: 'thin', color: { argb: 'B8CCE4' } } };
758
+ worksheet.getRow(rowNumber).height = 24;
759
+ }
760
+ else {
761
+ cell.font = { size: style === 'note' ? 10 : 11, color: { argb: '263238' } };
762
+ worksheet.getRow(rowNumber).height = Math.min(80, Math.max(20, Math.ceil(clean.length / 90) * 18));
763
+ }
764
+ return rowNumber + 1;
765
+ }
766
+ autosizeXlsxColumns(worksheet) {
767
+ worksheet.columns.forEach((column) => {
768
+ let maxLength = 10;
769
+ column.eachCell({ includeEmpty: false }, (cell) => {
770
+ const value = cell.value;
771
+ const text = typeof value === 'string'
772
+ ? value
773
+ : value === null || value === undefined
774
+ ? ''
775
+ : JSON.stringify(value);
776
+ maxLength = Math.max(maxLength, ...String(text).split(/\r?\n/).map((line) => line.length));
777
+ });
778
+ column.width = Math.min(48, Math.max(12, maxLength + 2));
779
+ });
780
+ }
781
+ async renderXlsx(content) {
782
+ const workbook = new ExcelJS.Workbook();
783
+ workbook.creator = 'Canvas Flow';
784
+ workbook.created = new Date();
785
+ workbook.modified = new Date();
786
+ const worksheet = workbook.addWorksheet('Documento', {
787
+ properties: { defaultRowHeight: 20 },
788
+ views: [{ state: 'frozen', ySplit: 1 }],
789
+ });
790
+ const directRows = this.structuredRowsForContent(content);
791
+ const blocks = this.parseRichDocumentContent(content);
792
+ const hasRichStructure = blocks.some((block) => block.type !== 'heading' && block.type !== 'paragraph') || blocks.some((block) => block.type === 'table');
793
+ let rowNumber = 1;
794
+ if (directRows && !hasRichStructure) {
795
+ rowNumber = this.writeXlsxTable(worksheet, directRows, rowNumber, 'Dados');
796
+ }
797
+ else {
798
+ blocks.forEach((block) => {
799
+ if (block.type === 'heading') {
800
+ rowNumber = this.writeXlsxMergedRow(worksheet, rowNumber, block.text, block.level <= 1 ? 'title' : 'heading');
801
+ }
802
+ else if (block.type === 'label') {
803
+ rowNumber = this.writeXlsxMergedRow(worksheet, rowNumber, `${block.label}: ${block.value}`, 'note');
804
+ }
805
+ else if (block.type === 'listItem') {
806
+ rowNumber = this.writeXlsxMergedRow(worksheet, rowNumber, `${block.marker || '-'} ${block.text}`, 'body');
807
+ }
808
+ else if (block.type === 'code') {
809
+ rowNumber = this.writeXlsxMergedRow(worksheet, rowNumber, block.lines.join('\n'), 'note');
810
+ }
811
+ else if (block.type === 'table') {
812
+ rowNumber = this.writeXlsxTable(worksheet, block.rows, rowNumber, '');
813
+ }
814
+ else {
815
+ rowNumber = this.writeXlsxMergedRow(worksheet, rowNumber, block.text, 'body');
816
+ }
817
+ });
818
+ }
819
+ if (rowNumber === 1)
820
+ this.writeXlsxMergedRow(worksheet, 1, 'Documento sem conteudo.', 'body');
821
+ this.autosizeXlsxColumns(worksheet);
822
+ return Buffer.from(await workbook.xlsx.writeBuffer());
823
+ }
824
+ xlsxWorksheet(workbook, edit) {
825
+ const sheet = String(edit.sheet || '').trim();
826
+ const sheetIndex = Math.max(0, Math.floor(Number(edit.sheetIndex || 0)));
827
+ const worksheet = sheet
828
+ ? workbook.getWorksheet(sheet)
829
+ : workbook.worksheets[sheetIndex];
830
+ if (!worksheet) {
831
+ throw new common_1.BadRequestException(sheet
832
+ ? `A aba "${sheet}" nao foi encontrada no XLSX.`
833
+ : `A aba de indice ${sheetIndex} nao foi encontrada no XLSX.`);
834
+ }
835
+ return worksheet;
836
+ }
837
+ xlsxDurationValue(value) {
838
+ if (typeof value === 'number' && Number.isFinite(value))
839
+ return value;
840
+ const match = String(value ?? '').trim().match(/^(\d+):([0-5]\d)(?::([0-5]\d))?$/);
841
+ if (!match) {
842
+ throw new common_1.BadRequestException(`Duracao XLSX invalida: "${String(value ?? '')}". Use HH:mm ou HH:mm:ss.`);
843
+ }
844
+ return (Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3] || 0)) / 86400;
845
+ }
846
+ xlsxCellValue(value, valueType) {
847
+ if (valueType === 'duration')
848
+ return this.xlsxDurationValue(value);
849
+ if (valueType === 'number') {
850
+ const numeric = Number(value);
851
+ if (!Number.isFinite(numeric)) {
852
+ throw new common_1.BadRequestException(`Numero XLSX invalido: "${String(value ?? '')}".`);
853
+ }
854
+ return numeric;
855
+ }
856
+ return value ?? '';
857
+ }
858
+ setXlsxCellValue(cell, value, edit) {
859
+ cell.value = this.xlsxCellValue(value, edit.valueType);
860
+ const numberFormat = String(edit.numberFormat || (edit.valueType === 'duration' ? '[h]:mm' : '')).trim();
861
+ if (numberFormat)
862
+ cell.numFmt = numberFormat;
863
+ }
864
+ xlsxColumnNumber(worksheet, column, headerRow) {
865
+ const numeric = Number(column);
866
+ if (Number.isInteger(numeric) && numeric > 0)
867
+ return numeric;
868
+ const expectedHeader = String(column || '').trim().toLowerCase();
869
+ if (!expectedHeader)
870
+ return 1;
871
+ const row = worksheet.getRow(headerRow);
872
+ for (let index = 1; index <= Math.max(worksheet.columnCount, row.cellCount); index += 1) {
873
+ if (String(row.getCell(index).text || '').trim().toLowerCase() === expectedHeader)
874
+ return index;
875
+ }
876
+ throw new common_1.BadRequestException(`A coluna "${String(column || '')}" nao foi encontrada na aba "${worksheet.name}".`);
877
+ }
878
+ applyXlsxEdits(workbook, edits) {
879
+ edits.slice(0, 1000).forEach((edit) => {
880
+ const worksheet = this.xlsxWorksheet(workbook, edit);
881
+ if (edit.type === 'set_cell') {
882
+ const address = String(edit.cell || '').trim().toUpperCase();
883
+ if (!/^[A-Z]{1,3}[1-9]\d{0,6}$/.test(address)) {
884
+ throw new common_1.BadRequestException(`Celula XLSX invalida: "${address}". Use um endereco como C2.`);
885
+ }
886
+ this.setXlsxCellValue(worksheet.getCell(address), edit.value, edit);
887
+ return;
888
+ }
889
+ if (edit.type === 'append_column') {
890
+ const headerRow = Math.max(1, Math.floor(Number(edit.headerRow || 1)));
891
+ const startRow = Math.max(headerRow + 1, Math.floor(Number(edit.startRow || headerRow + 1)));
892
+ const columnNumber = Math.max(1, worksheet.columnCount + 1);
893
+ worksheet.getRow(headerRow).getCell(columnNumber).value = String(edit.header || '');
894
+ if (edit.valuesByKey && typeof edit.valuesByKey === 'object' && !Array.isArray(edit.valuesByKey)) {
895
+ const keyColumn = this.xlsxColumnNumber(worksheet, edit.keyColumn || 1, headerRow);
896
+ for (let rowNumber = startRow; rowNumber <= worksheet.actualRowCount; rowNumber += 1) {
897
+ const key = String(worksheet.getRow(rowNumber).getCell(keyColumn).text || '').trim();
898
+ if (Object.prototype.hasOwnProperty.call(edit.valuesByKey, key)) {
899
+ this.setXlsxCellValue(worksheet.getRow(rowNumber).getCell(columnNumber), edit.valuesByKey[key], edit);
900
+ }
901
+ }
902
+ return;
903
+ }
904
+ (Array.isArray(edit.values) ? edit.values : []).forEach((value, index) => {
905
+ this.setXlsxCellValue(worksheet.getRow(startRow + index).getCell(columnNumber), value, edit);
906
+ });
907
+ }
908
+ });
909
+ return workbook;
910
+ }
911
+ async renderTemplateXlsx(templateDocumentId, replacements, scope, edits = []) {
912
+ const { buffer } = await this.getFile(templateDocumentId, scope);
913
+ try {
914
+ const workbook = new ExcelJS.Workbook();
915
+ await workbook.xlsx.load(buffer);
916
+ workbook.worksheets.forEach((worksheet) => {
917
+ worksheet.eachRow({ includeEmpty: false }, (row) => {
918
+ row.eachCell({ includeEmpty: false }, (cell) => {
919
+ if (typeof cell.value === 'string') {
920
+ cell.value = this.fillTextTemplate(cell.value, replacements || {});
921
+ }
922
+ });
923
+ });
924
+ });
925
+ this.applyXlsxEdits(workbook, edits);
926
+ return Buffer.from(await workbook.xlsx.writeBuffer());
927
+ }
928
+ catch (error) {
929
+ if (error instanceof common_1.BadRequestException)
930
+ throw error;
931
+ throw new common_1.BadRequestException(`Nao foi possivel preencher o template XLSX: ${error?.message || String(error)}`);
932
+ }
933
+ }
934
+ csvEscape(value) {
935
+ const text = String(value ?? '');
936
+ return /[",\r\n;]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
937
+ }
938
+ renderCsv(content) {
939
+ const rows = this.structuredRowsForContent(content);
940
+ if (rows?.length) {
941
+ return `${rows.map((row) => row.map((cell) => this.csvEscape(this.inlineText(cell))).join(',')).join('\n')}\n`;
942
+ }
943
+ const lines = String(content || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
944
+ if (!lines.length)
945
+ return '';
946
+ return `conteudo\n${lines.map((line) => this.csvEscape(this.inlineText(line))).join('\n')}\n`;
947
+ }
948
+ escapeHtml(value) {
949
+ return String(value ?? '')
950
+ .replace(/&/g, '&amp;')
951
+ .replace(/</g, '&lt;')
952
+ .replace(/>/g, '&gt;')
953
+ .replace(/"/g, '&quot;')
954
+ .replace(/'/g, '&#39;');
955
+ }
956
+ renderHtml(content) {
957
+ const blocks = this.parseRichDocumentContent(content);
958
+ const body = blocks.map((block) => {
959
+ if (block.type === 'heading') {
960
+ const tag = block.level <= 1 ? 'h1' : block.level === 2 ? 'h2' : 'h3';
961
+ return `<${tag}>${this.escapeHtml(this.inlineText(block.text))}</${tag}>`;
962
+ }
963
+ if (block.type === 'label') {
964
+ return `<p class="meta"><strong>${this.escapeHtml(this.inlineText(block.label))}:</strong> ${this.escapeHtml(this.inlineText(block.value))}</p>`;
965
+ }
966
+ if (block.type === 'listItem') {
967
+ return `<ul><li>${this.escapeHtml(this.inlineText(block.text))}</li></ul>`;
968
+ }
969
+ if (block.type === 'code') {
970
+ return `<pre><code>${this.escapeHtml(block.lines.join('\n'))}</code></pre>`;
971
+ }
972
+ if (block.type === 'table') {
973
+ const rows = this.normalizeXlsxRows(block.rows);
974
+ const [header, ...items] = rows;
975
+ return [
976
+ '<table>',
977
+ header ? `<thead><tr>${header.map((cell) => `<th>${this.escapeHtml(cell)}</th>`).join('')}</tr></thead>` : '',
978
+ `<tbody>${items.map((row) => `<tr>${row.map((cell) => `<td>${this.escapeHtml(cell)}</td>`).join('')}</tr>`).join('')}</tbody>`,
979
+ '</table>',
980
+ ].join('');
981
+ }
982
+ return `<p>${this.escapeHtml(this.inlineText(block.text))}</p>`;
983
+ }).join('\n');
984
+ return [
985
+ '<!doctype html>',
986
+ '<html lang="pt-BR">',
987
+ '<head>',
988
+ '<meta charset="utf-8" />',
989
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
990
+ '<title>Documento Canvas Flow</title>',
991
+ '<style>',
992
+ ':root{--blue:#17365d;--line:#cbd5e1;--muted:#64748b;--bg:#f8fafc;}',
993
+ 'body{margin:0;background:var(--bg);font-family:Inter,Segoe UI,Arial,sans-serif;color:#263238;line-height:1.55;}',
994
+ '.page{max-width:980px;margin:32px auto;padding:44px 52px;background:#fff;border:1px solid #e2e8f0;border-radius:18px;box-shadow:0 18px 45px rgba(15,23,42,.08);}',
995
+ '.eyebrow{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#607d8b;border-bottom:1px solid #d8dee8;padding-bottom:10px;margin-bottom:26px;}',
996
+ 'h1{font-size:34px;line-height:1.15;color:var(--blue);margin:0 0 18px;border-bottom:3px solid #1f6feb;padding-bottom:14px;}',
997
+ 'h2{font-size:24px;color:var(--blue);margin:34px 0 12px;border-bottom:1px solid #b8cce4;padding-bottom:8px;}',
998
+ 'h3{font-size:18px;color:#1f4e79;margin:24px 0 10px;}',
999
+ 'p{margin:10px 0;} .meta{color:#374151;margin:4px 0;}',
1000
+ 'ul{margin:10px 0 10px 22px;padding:0;} li{margin:6px 0;}',
1001
+ 'table{width:100%;border-collapse:collapse;margin:18px 0 26px;font-size:14px;border:1px solid var(--line);}',
1002
+ 'th{background:#eaf2ff;color:var(--blue);text-align:left;font-weight:700;} th,td{border:1px solid var(--line);padding:10px 12px;vertical-align:top;} tr:nth-child(even) td{background:#f8fafc;}',
1003
+ 'pre{background:#0f172a;color:#e2e8f0;border-radius:12px;padding:16px;overflow:auto;font-size:13px;}',
1004
+ '@media print{body{background:#fff}.page{box-shadow:none;border:0;margin:0;max-width:none;border-radius:0}}',
1005
+ '</style>',
1006
+ '</head>',
1007
+ '<body>',
1008
+ '<main class="page">',
1009
+ '<div class="eyebrow">Documento consolidado</div>',
1010
+ body || '<p>Documento sem conteudo.</p>',
1011
+ '</main>',
1012
+ '</body>',
1013
+ '</html>',
1014
+ ].join('\n');
1015
+ }
1016
+ pdfInlineText(value) {
1017
+ return this.inlineText(value);
1018
+ }
1019
+ pdfAvailableWidth(doc) {
1020
+ return doc.page.width - doc.page.margins.left - doc.page.margins.right;
1021
+ }
1022
+ ensurePdfSpace(doc, height) {
1023
+ const bottom = doc.page.height - doc.page.margins.bottom - 14;
1024
+ const nearTop = doc.y <= doc.page.margins.top + 6;
1025
+ if (!nearTop && doc.y + height > bottom)
1026
+ doc.addPage();
1027
+ }
1028
+ renderPdfHeading(doc, text, level) {
1029
+ const clean = this.pdfInlineText(text);
1030
+ if (!clean)
1031
+ return;
1032
+ const left = doc.page.margins.left;
1033
+ const width = this.pdfAvailableWidth(doc);
1034
+ const fontSize = level <= 1 ? 19 : level === 2 ? 14 : 11.5;
1035
+ const before = level <= 1 ? 2 : level === 2 ? 1.1 : 0.7;
1036
+ const after = level <= 1 ? 0.8 : 0.45;
1037
+ const estimatedHeight = doc.font('Helvetica-Bold').fontSize(fontSize).heightOfString(clean, { width }) + 18;
1038
+ this.ensurePdfSpace(doc, estimatedHeight);
1039
+ if (doc.y > doc.page.margins.top + 6)
1040
+ doc.moveDown(before);
1041
+ doc.font('Helvetica-Bold').fontSize(fontSize).fillColor(level <= 2 ? '#17365d' : '#1f4e79');
1042
+ doc.text(clean, left, doc.y, { width, lineGap: 1 });
1043
+ if (level <= 2) {
1044
+ doc.moveDown(0.25);
1045
+ doc.moveTo(doc.page.margins.left, doc.y)
1046
+ .lineTo(doc.page.width - doc.page.margins.right, doc.y)
1047
+ .strokeColor(level === 1 ? '#1f6feb' : '#b8cce4')
1048
+ .lineWidth(level === 1 ? 1.5 : 0.8)
1049
+ .stroke();
1050
+ }
1051
+ doc.moveDown(after);
1052
+ }
1053
+ renderPdfParagraph(doc, text) {
1054
+ const clean = this.pdfInlineText(text);
1055
+ if (!clean)
1056
+ return;
1057
+ const left = doc.page.margins.left;
1058
+ const width = this.pdfAvailableWidth(doc);
1059
+ doc.font('Helvetica').fontSize(10).fillColor('#263238');
1060
+ this.ensurePdfSpace(doc, Math.min(doc.heightOfString(clean, { width, lineGap: 2 }), 120) + 8);
1061
+ doc.text(clean, left, doc.y, { width, lineGap: 2, align: 'left' });
1062
+ doc.moveDown(0.55);
1063
+ }
1064
+ renderPdfLabel(doc, label, value) {
1065
+ const cleanLabel = this.pdfInlineText(label);
1066
+ const cleanValue = this.pdfInlineText(value);
1067
+ if (!cleanLabel)
1068
+ return;
1069
+ const left = doc.page.margins.left;
1070
+ const width = this.pdfAvailableWidth(doc);
1071
+ this.ensurePdfSpace(doc, 20);
1072
+ doc.font('Helvetica-Bold').fontSize(9.5).fillColor('#374151').text(`${cleanLabel}: `, left, doc.y, {
1073
+ width,
1074
+ continued: Boolean(cleanValue),
1075
+ lineGap: 1,
1076
+ });
1077
+ if (cleanValue) {
1078
+ doc.font('Helvetica').fontSize(9.5).fillColor('#374151').text(cleanValue, { width, lineGap: 1 });
1079
+ }
1080
+ doc.moveDown(0.15);
1081
+ }
1082
+ renderPdfListItem(doc, text, marker = '') {
1083
+ const clean = this.pdfInlineText(text);
1084
+ if (!clean)
1085
+ return;
1086
+ const left = doc.page.margins.left;
1087
+ const contentLeft = left + 16;
1088
+ const width = this.pdfAvailableWidth(doc) - 16;
1089
+ doc.font('Helvetica').fontSize(9.8);
1090
+ const height = doc.heightOfString(clean, { width, lineGap: 1.5 });
1091
+ this.ensurePdfSpace(doc, height + 6);
1092
+ const y = doc.y;
1093
+ if (marker) {
1094
+ doc.font('Helvetica-Bold').fontSize(9.2).fillColor('#1f4e79').text(marker, left, y, { width: 13, lineBreak: false });
1095
+ }
1096
+ else {
1097
+ doc.circle(left + 4, y + 5, 2).fill('#1f6feb');
1098
+ }
1099
+ doc.font('Helvetica').fontSize(9.8).fillColor('#263238').text(clean, contentLeft, y, { width, lineGap: 1.5 });
1100
+ doc.y = Math.max(doc.y, y + height + 4);
1101
+ }
1102
+ renderPdfCodeBlock(doc, lines) {
1103
+ for (let offset = 0; offset < lines.length; offset += 30) {
1104
+ const content = lines.slice(offset, offset + 30).join('\n').trimEnd();
1105
+ if (!content)
1106
+ continue;
1107
+ const left = doc.page.margins.left;
1108
+ const width = this.pdfAvailableWidth(doc);
1109
+ doc.font('Courier').fontSize(8.2);
1110
+ const height = doc.heightOfString(content, { width: width - 18, lineGap: 1 }) + 18;
1111
+ this.ensurePdfSpace(doc, height + 6);
1112
+ const y = doc.y;
1113
+ doc.roundedRect(left, y, width, height, 4).fillAndStroke('#f5f7fa', '#d8dee8');
1114
+ doc.font('Courier').fontSize(8.2).fillColor('#263238').text(content, left + 9, y + 9, {
1115
+ width: width - 18,
1116
+ lineGap: 1,
1117
+ });
1118
+ doc.y = y + height + 7;
1119
+ }
1120
+ }
1121
+ renderPdfTable(doc, rows) {
1122
+ const columnCount = Math.max(2, Math.min(8, ...rows.map((row) => row.length)));
1123
+ const normalizedRows = rows.slice(0, 200).map((row) => (Array.from({ length: columnCount }, (_, index) => this.pdfInlineText(row[index] || ''))));
1124
+ const left = doc.page.margins.left;
1125
+ const width = this.pdfAvailableWidth(doc);
1126
+ const columnWidth = width / columnCount;
1127
+ const paddingX = 6;
1128
+ const paddingY = 5;
1129
+ const rowHeight = (row, isHeader) => {
1130
+ doc.font(isHeader ? 'Helvetica-Bold' : 'Helvetica').fontSize(isHeader ? 8.8 : 8.6);
1131
+ return Math.max(24, ...row.map((cell) => (doc.heightOfString(cell || ' ', { width: columnWidth - paddingX * 2, lineGap: 1 }) + paddingY * 2)));
1132
+ };
1133
+ const drawRow = (row, rowIndex, repeatedHeader = false) => {
1134
+ const isHeader = rowIndex === 0;
1135
+ const height = rowHeight(row, isHeader);
1136
+ const bottom = doc.page.height - doc.page.margins.bottom - 14;
1137
+ if (doc.y + height > bottom) {
1138
+ doc.addPage();
1139
+ if (!isHeader && !repeatedHeader && normalizedRows[0]) {
1140
+ drawRow(normalizedRows[0], 0, true);
1141
+ }
1142
+ }
1143
+ const y = doc.y;
1144
+ let x = left;
1145
+ row.forEach((cell) => {
1146
+ const fill = isHeader ? '#eaf2ff' : rowIndex % 2 === 0 ? '#ffffff' : '#f8fafc';
1147
+ doc.rect(x, y, columnWidth, height).fillAndStroke(fill, '#cbd5e1');
1148
+ doc.font(isHeader ? 'Helvetica-Bold' : 'Helvetica')
1149
+ .fontSize(isHeader ? 8.8 : 8.6)
1150
+ .fillColor(isHeader ? '#17365d' : '#263238')
1151
+ .text(cell || ' ', x + paddingX, y + paddingY, {
1152
+ width: columnWidth - paddingX * 2,
1153
+ lineGap: 1,
1154
+ });
1155
+ x += columnWidth;
1156
+ });
1157
+ doc.y = y + height;
1158
+ };
1159
+ this.ensurePdfSpace(doc, 34);
1160
+ normalizedRows.forEach((row, rowIndex) => drawRow(row, rowIndex));
1161
+ doc.moveDown(0.85);
1162
+ }
1163
+ drawPdfPageDecoration(doc) {
1164
+ const range = doc.bufferedPageRange();
1165
+ for (let pageIndex = range.start; pageIndex < range.start + range.count; pageIndex += 1) {
1166
+ doc.switchToPage(pageIndex);
1167
+ const left = doc.page.margins.left;
1168
+ const right = doc.page.width - doc.page.margins.right;
1169
+ doc.save();
1170
+ doc.moveTo(left, 34).lineTo(right, 34).strokeColor('#d8dee8').lineWidth(0.7).stroke();
1171
+ doc.font('Helvetica-Bold').fontSize(7.5).fillColor('#607d8b')
1172
+ .text('DOCUMENTO CONSOLIDADO', left, 22, { width: right - left, lineBreak: false });
1173
+ doc.moveTo(left, doc.page.height - 33).lineTo(right, doc.page.height - 33).strokeColor('#d8dee8').lineWidth(0.7).stroke();
1174
+ doc.font('Helvetica').fontSize(8.5).fillColor('#455a64')
1175
+ .text(`Pagina ${pageIndex - range.start + 1} de ${range.count}`, right - 100, doc.page.height - 48, {
1176
+ width: 100,
1177
+ align: 'right',
1178
+ });
1179
+ doc.restore();
1180
+ }
1181
+ }
1182
+ async renderPdf(content) {
1183
+ const doc = new PDFDocument({
1184
+ size: 'A4',
1185
+ margins: { top: 54, right: 52, bottom: 54, left: 52 },
1186
+ bufferPages: true,
1187
+ info: { Title: 'Documento consolidado' },
1188
+ });
1189
+ const chunks = [];
1190
+ doc.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
1191
+ const completed = new Promise((resolveBuffer, reject) => {
1192
+ doc.on('end', () => resolveBuffer(Buffer.concat(chunks)));
1193
+ doc.on('error', reject);
1194
+ });
1195
+ const blocks = this.parseRichDocumentContent(content);
1196
+ blocks.forEach((block) => {
1197
+ if (block.type === 'heading')
1198
+ this.renderPdfHeading(doc, block.text, block.level);
1199
+ if (block.type === 'paragraph')
1200
+ this.renderPdfParagraph(doc, block.text);
1201
+ if (block.type === 'label')
1202
+ this.renderPdfLabel(doc, block.label, block.value);
1203
+ if (block.type === 'listItem')
1204
+ this.renderPdfListItem(doc, block.text, block.marker);
1205
+ if (block.type === 'code')
1206
+ this.renderPdfCodeBlock(doc, block.lines);
1207
+ if (block.type === 'table')
1208
+ this.renderPdfTable(doc, block.rows);
1209
+ });
1210
+ if (!blocks.length)
1211
+ this.renderPdfParagraph(doc, ' ');
1212
+ this.drawPdfPageDecoration(doc);
1213
+ doc.end();
1214
+ return await completed;
1215
+ }
1216
+ async renderArtifact(params) {
1217
+ const renderedContent = this.fillTextTemplate(params.content || '', params.replacements || {});
1218
+ const content = this.unwrapGeneratedDocumentContent(renderedContent);
1219
+ if (params.format === 'docx') {
1220
+ if (params.templateDocumentId) {
1221
+ return await this.renderTemplateDocx(params.templateDocumentId, params.replacements || {}, params.scope || {}, params.docxEdits || [], params.content || '');
1222
+ }
1223
+ return await this.renderNewDocx(content);
1224
+ }
1225
+ if (params.format === 'xlsx') {
1226
+ if (params.templateDocumentId) {
1227
+ return await this.renderTemplateXlsx(params.templateDocumentId, params.replacements || {}, params.scope || {}, params.xlsxEdits || []);
1228
+ }
1229
+ return await this.renderXlsx(content);
1230
+ }
1231
+ if (params.format === 'pdf')
1232
+ return await this.renderPdf(content);
1233
+ if (params.format === 'html')
1234
+ return Buffer.from(this.renderHtml(content), 'utf-8');
1235
+ if (params.format === 'csv')
1236
+ return Buffer.from(this.renderCsv(content), 'utf-8');
1237
+ if (params.format === 'json') {
1238
+ try {
1239
+ return Buffer.from(`${JSON.stringify(JSON.parse(content), null, 2)}\n`, 'utf-8');
1240
+ }
1241
+ catch {
1242
+ return Buffer.from(content, 'utf-8');
1243
+ }
1244
+ }
1245
+ return Buffer.from(content, 'utf-8');
1246
+ }
1247
+ async storeOriginal(params) {
1248
+ const documentId = (0, crypto_1.randomUUID)();
1249
+ const scope = params.scope || {};
1250
+ const filename = this.safeSegment((0, path_1.basename)(params.filename || 'arquivo.bin'), 'arquivo.bin');
1251
+ const storage = this.storageMode();
1252
+ const key = this.fileKey(documentId, filename, scope);
1253
+ const stored = await this.writeBytes(storage, key, params.buffer, params.mimeType || 'application/octet-stream');
1254
+ const row = await new this.model({
1255
+ documentId,
1256
+ organizationId: String(scope.organizationId || ''),
1257
+ agentId: String(scope.agentId || ''),
1258
+ flowId: String(scope.flowId || ''),
1259
+ conversationId: String(scope.conversationId || ''),
1260
+ rootDocumentId: documentId,
1261
+ parentDocumentId: '',
1262
+ version: 1,
1263
+ filename,
1264
+ mimeType: params.mimeType || 'application/octet-stream',
1265
+ size: params.buffer.length,
1266
+ storage,
1267
+ bucket: stored.bucket,
1268
+ key,
1269
+ source: params.source || 'upload',
1270
+ status: 'stored',
1271
+ text: params.text || '',
1272
+ structure: params.structure || {},
1273
+ metadata: {
1274
+ ...(params.metadata || {}),
1275
+ hashSha256: (0, crypto_1.createHash)('sha256').update(params.buffer).digest('hex'),
1276
+ versionId: stored.versionId,
1277
+ etag: stored.etag,
1278
+ },
1279
+ }).save();
1280
+ return this.toPlain(row);
1281
+ }
1282
+ async updateExtraction(documentId, extraction) {
1283
+ const row = await this.model.findOneAndUpdate({ documentId }, {
1284
+ $set: {
1285
+ text: extraction.text || '',
1286
+ structure: extraction.structure || {},
1287
+ status: extraction.text ? 'ready' : 'stored',
1288
+ ...(extraction.metadata ? { metadata: extraction.metadata } : {}),
1289
+ },
1290
+ }, { new: true }).lean().exec();
1291
+ return row ? this.toPlain(row) : null;
1292
+ }
1293
+ async createArtifact(params) {
1294
+ const format = this.assertArtifactFormat(params.format);
1295
+ const scope = params.scope || {};
1296
+ const parent = params.parentDocumentId
1297
+ ? await this.getRecord(params.parentDocumentId, scope)
1298
+ : null;
1299
+ const templateDocumentId = params.templateDocumentId || '';
1300
+ const buffer = await this.renderArtifact({
1301
+ format,
1302
+ content: params.content,
1303
+ replacements: params.replacements,
1304
+ templateDocumentId,
1305
+ docxEdits: params.docxEdits,
1306
+ xlsxEdits: params.xlsxEdits,
1307
+ scope,
1308
+ });
1309
+ if (!buffer.length) {
1310
+ throw new common_1.BadRequestException('Nao foi possivel gerar o artefato: o conteudo resultou em um arquivo vazio.');
1311
+ }
1312
+ const documentId = (0, crypto_1.randomUUID)();
1313
+ const filename = this.safeSegment(params.filename || `artefato-${documentId.slice(0, 8)}.${format}`, `artefato.${format}`);
1314
+ const storage = this.storageMode();
1315
+ const key = this.fileKey(documentId, filename, scope);
1316
+ const mimeType = this.contentTypeFor(format);
1317
+ const stored = await this.writeBytes(storage, key, buffer, mimeType);
1318
+ const rootDocumentId = String(parent?.rootDocumentId || parent?.documentId || documentId);
1319
+ const version = parent ? Number(parent.version || 1) + 1 : 1;
1320
+ const row = await new this.model({
1321
+ documentId,
1322
+ organizationId: String(scope.organizationId || ''),
1323
+ agentId: String(scope.agentId || ''),
1324
+ flowId: String(scope.flowId || ''),
1325
+ conversationId: String(scope.conversationId || ''),
1326
+ rootDocumentId,
1327
+ parentDocumentId: String(parent?.documentId || ''),
1328
+ version,
1329
+ filename,
1330
+ mimeType,
1331
+ size: buffer.length,
1332
+ storage,
1333
+ bucket: stored.bucket,
1334
+ key,
1335
+ source: 'generated',
1336
+ status: 'ready',
1337
+ text: params.content || '',
1338
+ structure: {
1339
+ format,
1340
+ templateDocumentId,
1341
+ replacements: params.replacements || {},
1342
+ docxEdits: params.docxEdits || [],
1343
+ xlsxEdits: params.xlsxEdits || [],
1344
+ },
1345
+ metadata: {
1346
+ ...(params.metadata || {}),
1347
+ hashSha256: (0, crypto_1.createHash)('sha256').update(buffer).digest('hex'),
1348
+ versionId: stored.versionId,
1349
+ etag: stored.etag,
1350
+ },
1351
+ }).save();
1352
+ return await this.withSignedDownloadUrl(this.toPlain(row));
1353
+ }
1354
+ async getRecord(documentId, scope = {}) {
1355
+ const query = { documentId };
1356
+ if (scope.organizationId)
1357
+ query.organizationId = scope.organizationId;
1358
+ const row = await this.model.findOne(query).lean().exec();
1359
+ if (!row)
1360
+ throw new common_1.NotFoundException('Documento nao encontrado.');
1361
+ return this.toPlain(row);
1362
+ }
1363
+ async getFile(documentId, scope = {}) {
1364
+ const record = await this.getRecord(documentId, scope);
1365
+ if (record.storage === 's3') {
1366
+ const response = await this.getS3Client().send(new client_s3_1.GetObjectCommand({
1367
+ Bucket: record.bucket || this.s3Bucket(),
1368
+ Key: record.key,
1369
+ }));
1370
+ return { record, buffer: await this.streamToBuffer(response.Body) };
1371
+ }
1372
+ return { record, buffer: await fs_1.promises.readFile(this.resolveLocalPath(record.key)) };
1373
+ }
1374
+ async openLocalReadStream(documentId, scope = {}) {
1375
+ const record = await this.getRecord(documentId, scope);
1376
+ if (record.storage !== 'local')
1377
+ return null;
1378
+ return { record, stream: (0, fs_1.createReadStream)(this.resolveLocalPath(record.key)) };
1379
+ }
1380
+ async withSignedDownloadUrl(record) {
1381
+ if (record.storage !== 's3') {
1382
+ const prefix = this.publicApiUrl();
1383
+ return { ...record, downloadUrl: `${prefix}${record.downloadPath}` };
1384
+ }
1385
+ const expiresIn = Math.max(60, Math.min(Number(this.configService.get('CANVAS_FLOW_FILES_DOWNLOAD_TTL_SECONDS') || 900), 86400));
1386
+ const downloadUrl = await (0, s3_request_presigner_1.getSignedUrl)(this.getS3Client(), new client_s3_1.GetObjectCommand({
1387
+ Bucket: record.bucket || this.s3Bucket(),
1388
+ Key: record.key,
1389
+ ResponseContentDisposition: `attachment; filename="${this.safeSegment(record.filename, 'arquivo.bin')}"`,
1390
+ }), { expiresIn });
1391
+ return { ...record, downloadUrl, downloadExpiresInSeconds: expiresIn };
1392
+ }
1393
+ async getDownloadInfo(documentId, scope = {}) {
1394
+ return await this.withSignedDownloadUrl(await this.getRecord(documentId, scope));
1395
+ }
1396
+ async list(scope = {}, limit = 100) {
1397
+ const query = {};
1398
+ if (scope.organizationId)
1399
+ query.organizationId = scope.organizationId;
1400
+ if (scope.agentId)
1401
+ query.agentId = scope.agentId;
1402
+ if (scope.flowId)
1403
+ query.flowId = scope.flowId;
1404
+ if (scope.conversationId)
1405
+ query.conversationId = scope.conversationId;
1406
+ const rows = await this.model.find(query).sort({ createdAt: -1 }).limit(Math.max(1, Math.min(Number(limit || 100), 500))).lean().exec();
1407
+ return { documents: rows.map((row) => this.toPlain(row)), total: rows.length };
1408
+ }
1409
+ };
1410
+ exports.DocumentsService = DocumentsService;
1411
+ exports.DocumentsService = DocumentsService = __decorate([
1412
+ (0, common_1.Injectable)(),
1413
+ __param(0, (0, common_1.Inject)(documents_constants_model_1.MODEL_NAME)),
1414
+ __metadata("design:paramtypes", [mongoose_1.Model,
1415
+ config_1.ConfigService])
1416
+ ], DocumentsService);
1417
+ //# sourceMappingURL=documents-service.js.map