@juspay/neurolink 9.32.0 → 9.32.1

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 (467) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/auth/anthropicOAuth.js +1 -1
  3. package/dist/cli/commands/proxy.js +18 -5
  4. package/dist/client/aiSdkAdapter.js +1 -1
  5. package/dist/client/index.js +137 -501
  6. package/dist/core/factory.js +0 -1
  7. package/dist/core/redisConversationMemoryManager.js +1 -1
  8. package/dist/features/ppt/slideGenerator.js +0 -1
  9. package/dist/features/ppt/utils.js +0 -1
  10. package/dist/lib/server/routes/claudeProxyRoutes.js +45 -9
  11. package/dist/mcp/elicitationProtocol.js +1 -1
  12. package/dist/mcp/servers/agent/directToolsServer.js +0 -1
  13. package/dist/providers/azureOpenai.js +1 -1
  14. package/dist/providers/huggingFace.js +0 -1
  15. package/dist/providers/openaiCompatible.js +0 -1
  16. package/dist/sdk/toolRegistration.js +0 -1
  17. package/dist/server/openapi/generator.js +1 -1
  18. package/dist/server/routes/claudeProxyRoutes.js +45 -9
  19. package/dist/types/configTypes.js +0 -5
  20. package/dist/types/modelTypes.js +0 -1
  21. package/dist/types/tools.js +0 -1
  22. package/dist/types/typeAliases.js +0 -1
  23. package/dist/types/utilities.js +1 -1
  24. package/dist/types/workflowTypes.js +0 -1
  25. package/dist/utils/providerRetry.js +0 -1
  26. package/dist/utils/providerUtils.js +0 -1
  27. package/package.json +2 -2
  28. package/dist/client/adapters/providerImageAdapter.js +0 -588
  29. package/dist/client/adapters/tts/googleTTSHandler.js +0 -344
  30. package/dist/client/adapters/video/directorPipeline.js +0 -516
  31. package/dist/client/adapters/video/ffmpegAdapter.js +0 -206
  32. package/dist/client/adapters/video/frameExtractor.js +0 -143
  33. package/dist/client/adapters/video/vertexVideoHandler.js +0 -763
  34. package/dist/client/adapters/video/videoAnalyzer.js +0 -238
  35. package/dist/client/adapters/video/videoMerger.js +0 -171
  36. package/dist/client/agent/directTools.js +0 -840
  37. package/dist/client/auth/AuthProviderFactory.js +0 -111
  38. package/dist/client/auth/AuthProviderRegistry.js +0 -190
  39. package/dist/client/auth/RequestContext.js +0 -78
  40. package/dist/client/auth/accountPool.js +0 -178
  41. package/dist/client/auth/anthropicOAuth.js +0 -974
  42. package/dist/client/auth/authContext.js +0 -314
  43. package/dist/client/auth/errors.js +0 -39
  44. package/dist/client/auth/index.js +0 -61
  45. package/dist/client/auth/middleware/AuthMiddleware.js +0 -519
  46. package/dist/client/auth/middleware/rateLimitByUser.js +0 -554
  47. package/dist/client/auth/providers/BaseAuthProvider.js +0 -723
  48. package/dist/client/auth/providers/CognitoProvider.js +0 -304
  49. package/dist/client/auth/providers/KeycloakProvider.js +0 -393
  50. package/dist/client/auth/providers/auth0.js +0 -274
  51. package/dist/client/auth/providers/betterAuth.js +0 -182
  52. package/dist/client/auth/providers/clerk.js +0 -317
  53. package/dist/client/auth/providers/custom.js +0 -112
  54. package/dist/client/auth/providers/firebase.js +0 -226
  55. package/dist/client/auth/providers/jwt.js +0 -212
  56. package/dist/client/auth/providers/oauth2.js +0 -303
  57. package/dist/client/auth/providers/supabase.js +0 -259
  58. package/dist/client/auth/providers/workos.js +0 -284
  59. package/dist/client/auth/serverBridge.js +0 -25
  60. package/dist/client/auth/sessionManager.js +0 -437
  61. package/dist/client/auth/tokenStore.js +0 -799
  62. package/dist/client/client/aiSdkAdapter.js +0 -487
  63. package/dist/client/client/auth.js +0 -473
  64. package/dist/client/client/errors.js +0 -552
  65. package/dist/client/client/httpClient.js +0 -837
  66. package/dist/client/client/index.js +0 -172
  67. package/dist/client/client/interceptors.js +0 -601
  68. package/dist/client/client/sseClient.js +0 -545
  69. package/dist/client/client/streamingClient.js +0 -917
  70. package/dist/client/client/wsClient.js +0 -369
  71. package/dist/client/config/configManager.js +0 -303
  72. package/dist/client/config/conversationMemory.js +0 -86
  73. package/dist/client/config/taskClassificationConfig.js +0 -148
  74. package/dist/client/constants/contextWindows.js +0 -295
  75. package/dist/client/constants/enums.js +0 -853
  76. package/dist/client/constants/index.js +0 -207
  77. package/dist/client/constants/performance.js +0 -389
  78. package/dist/client/constants/retry.js +0 -266
  79. package/dist/client/constants/timeouts.js +0 -182
  80. package/dist/client/constants/tokens.js +0 -380
  81. package/dist/client/constants/videoErrors.js +0 -46
  82. package/dist/client/context/budgetChecker.js +0 -98
  83. package/dist/client/context/contextCompactor.js +0 -205
  84. package/dist/client/context/emergencyTruncation.js +0 -88
  85. package/dist/client/context/errorDetection.js +0 -171
  86. package/dist/client/context/errors.js +0 -21
  87. package/dist/client/context/fileTokenBudget.js +0 -127
  88. package/dist/client/context/prompts/summarizationPrompt.js +0 -117
  89. package/dist/client/context/stages/fileReadDeduplicator.js +0 -66
  90. package/dist/client/context/stages/slidingWindowTruncator.js +0 -190
  91. package/dist/client/context/stages/structuredSummarizer.js +0 -99
  92. package/dist/client/context/stages/toolOutputPruner.js +0 -52
  93. package/dist/client/context/summarizationEngine.js +0 -136
  94. package/dist/client/context/toolOutputLimits.js +0 -78
  95. package/dist/client/context/toolPairRepair.js +0 -66
  96. package/dist/client/core/analytics.js +0 -88
  97. package/dist/client/core/baseProvider.js +0 -1385
  98. package/dist/client/core/constants.js +0 -140
  99. package/dist/client/core/conversationMemoryFactory.js +0 -141
  100. package/dist/client/core/conversationMemoryInitializer.js +0 -128
  101. package/dist/client/core/conversationMemoryManager.js +0 -344
  102. package/dist/client/core/dynamicModels.js +0 -358
  103. package/dist/client/core/evaluation.js +0 -309
  104. package/dist/client/core/evaluationProviders.js +0 -248
  105. package/dist/client/core/factory.js +0 -412
  106. package/dist/client/core/infrastructure/baseError.js +0 -22
  107. package/dist/client/core/infrastructure/baseFactory.js +0 -54
  108. package/dist/client/core/infrastructure/baseRegistry.js +0 -53
  109. package/dist/client/core/infrastructure/index.js +0 -5
  110. package/dist/client/core/infrastructure/retry.js +0 -20
  111. package/dist/client/core/infrastructure/typedEventEmitter.js +0 -23
  112. package/dist/client/core/modelConfiguration.js +0 -851
  113. package/dist/client/core/modules/GenerationHandler.js +0 -588
  114. package/dist/client/core/modules/MessageBuilder.js +0 -273
  115. package/dist/client/core/modules/StreamHandler.js +0 -185
  116. package/dist/client/core/modules/TelemetryHandler.js +0 -203
  117. package/dist/client/core/modules/ToolsManager.js +0 -499
  118. package/dist/client/core/modules/Utilities.js +0 -331
  119. package/dist/client/core/redisConversationMemoryManager.js +0 -1435
  120. package/dist/client/core/streamAnalytics.js +0 -131
  121. package/dist/client/evaluation/contextBuilder.js +0 -134
  122. package/dist/client/evaluation/index.js +0 -61
  123. package/dist/client/evaluation/prompts.js +0 -73
  124. package/dist/client/evaluation/ragasEvaluator.js +0 -110
  125. package/dist/client/evaluation/retryManager.js +0 -78
  126. package/dist/client/evaluation/scoring.js +0 -61
  127. package/dist/client/factories/providerFactory.js +0 -166
  128. package/dist/client/factories/providerRegistry.js +0 -166
  129. package/dist/client/features/ppt/constants.js +0 -896
  130. package/dist/client/features/ppt/contentPlanner.js +0 -529
  131. package/dist/client/features/ppt/presentationOrchestrator.js +0 -236
  132. package/dist/client/features/ppt/slideGenerator.js +0 -532
  133. package/dist/client/features/ppt/slideRenderers.js +0 -2383
  134. package/dist/client/features/ppt/slideTypeInference.js +0 -405
  135. package/dist/client/features/ppt/types.js +0 -13
  136. package/dist/client/features/ppt/utils.js +0 -443
  137. package/dist/client/files/fileReferenceRegistry.js +0 -1543
  138. package/dist/client/files/fileTools.js +0 -450
  139. package/dist/client/files/streamingReader.js +0 -321
  140. package/dist/client/files/types.js +0 -23
  141. package/dist/client/hitl/hitlErrors.js +0 -54
  142. package/dist/client/hitl/hitlManager.js +0 -460
  143. package/dist/client/mcp/agentExposure.js +0 -356
  144. package/dist/client/mcp/auth/index.js +0 -11
  145. package/dist/client/mcp/auth/oauthClientProvider.js +0 -325
  146. package/dist/client/mcp/auth/tokenStorage.js +0 -134
  147. package/dist/client/mcp/batching/index.js +0 -10
  148. package/dist/client/mcp/batching/requestBatcher.js +0 -441
  149. package/dist/client/mcp/caching/index.js +0 -10
  150. package/dist/client/mcp/caching/toolCache.js +0 -433
  151. package/dist/client/mcp/elicitation/elicitationManager.js +0 -376
  152. package/dist/client/mcp/elicitation/index.js +0 -11
  153. package/dist/client/mcp/elicitation/types.js +0 -10
  154. package/dist/client/mcp/elicitationProtocol.js +0 -375
  155. package/dist/client/mcp/enhancedToolDiscovery.js +0 -481
  156. package/dist/client/mcp/externalServerManager.js +0 -1478
  157. package/dist/client/mcp/factory.js +0 -161
  158. package/dist/client/mcp/flexibleToolValidator.js +0 -161
  159. package/dist/client/mcp/httpRateLimiter.js +0 -391
  160. package/dist/client/mcp/httpRetryHandler.js +0 -178
  161. package/dist/client/mcp/index.js +0 -74
  162. package/dist/client/mcp/mcpCircuitBreaker.js +0 -427
  163. package/dist/client/mcp/mcpClientFactory.js +0 -708
  164. package/dist/client/mcp/mcpRegistryClient.js +0 -488
  165. package/dist/client/mcp/mcpServerBase.js +0 -373
  166. package/dist/client/mcp/multiServerManager.js +0 -579
  167. package/dist/client/mcp/registry.js +0 -158
  168. package/dist/client/mcp/routing/index.js +0 -10
  169. package/dist/client/mcp/routing/toolRouter.js +0 -416
  170. package/dist/client/mcp/serverCapabilities.js +0 -502
  171. package/dist/client/mcp/servers/agent/directToolsServer.js +0 -150
  172. package/dist/client/mcp/toolAnnotations.js +0 -239
  173. package/dist/client/mcp/toolConverter.js +0 -258
  174. package/dist/client/mcp/toolDiscoveryService.js +0 -798
  175. package/dist/client/mcp/toolIntegration.js +0 -334
  176. package/dist/client/mcp/toolRegistry.js +0 -729
  177. package/dist/client/memory/hippocampusInitializer.js +0 -19
  178. package/dist/client/memory/memoryRetrievalTools.js +0 -166
  179. package/dist/client/middleware/builtin/analytics.js +0 -132
  180. package/dist/client/middleware/builtin/autoEvaluation.js +0 -203
  181. package/dist/client/middleware/builtin/guardrails.js +0 -109
  182. package/dist/client/middleware/builtin/lifecycle.js +0 -168
  183. package/dist/client/middleware/factory.js +0 -327
  184. package/dist/client/middleware/registry.js +0 -295
  185. package/dist/client/middleware/utils/guardrailsUtils.js +0 -396
  186. package/dist/client/models/anthropicModels.js +0 -527
  187. package/dist/client/neurolink.js +0 -8233
  188. package/dist/client/observability/exporterRegistry.js +0 -413
  189. package/dist/client/observability/exporters/arizeExporter.js +0 -138
  190. package/dist/client/observability/exporters/baseExporter.js +0 -190
  191. package/dist/client/observability/exporters/braintrustExporter.js +0 -154
  192. package/dist/client/observability/exporters/datadogExporter.js +0 -196
  193. package/dist/client/observability/exporters/laminarExporter.js +0 -302
  194. package/dist/client/observability/exporters/langfuseExporter.js +0 -209
  195. package/dist/client/observability/exporters/langsmithExporter.js +0 -143
  196. package/dist/client/observability/exporters/otelExporter.js +0 -164
  197. package/dist/client/observability/exporters/posthogExporter.js +0 -287
  198. package/dist/client/observability/exporters/sentryExporter.js +0 -165
  199. package/dist/client/observability/index.js +0 -31
  200. package/dist/client/observability/metricsAggregator.js +0 -556
  201. package/dist/client/observability/otelBridge.js +0 -131
  202. package/dist/client/observability/retryPolicy.js +0 -383
  203. package/dist/client/observability/sampling/samplers.js +0 -216
  204. package/dist/client/observability/spanProcessor.js +0 -303
  205. package/dist/client/observability/tokenTracker.js +0 -413
  206. package/dist/client/observability/types/exporterTypes.js +0 -5
  207. package/dist/client/observability/types/index.js +0 -4
  208. package/dist/client/observability/types/spanTypes.js +0 -92
  209. package/dist/client/observability/utils/safeMetadata.js +0 -25
  210. package/dist/client/observability/utils/spanSerializer.js +0 -292
  211. package/dist/client/processors/archive/ArchiveProcessor.js +0 -1308
  212. package/dist/client/processors/base/BaseFileProcessor.js +0 -614
  213. package/dist/client/processors/base/types.js +0 -82
  214. package/dist/client/processors/config/fileTypes.js +0 -520
  215. package/dist/client/processors/config/index.js +0 -92
  216. package/dist/client/processors/config/languageMap.js +0 -410
  217. package/dist/client/processors/config/mimeTypes.js +0 -363
  218. package/dist/client/processors/config/sizeLimits.js +0 -258
  219. package/dist/client/processors/document/ExcelProcessor.js +0 -590
  220. package/dist/client/processors/document/OpenDocumentProcessor.js +0 -212
  221. package/dist/client/processors/document/PptxProcessor.js +0 -157
  222. package/dist/client/processors/document/RtfProcessor.js +0 -361
  223. package/dist/client/processors/document/WordProcessor.js +0 -353
  224. package/dist/client/processors/errors/FileErrorCode.js +0 -255
  225. package/dist/client/processors/errors/errorHelpers.js +0 -386
  226. package/dist/client/processors/errors/errorSerializer.js +0 -507
  227. package/dist/client/processors/errors/index.js +0 -49
  228. package/dist/client/processors/markup/SvgProcessor.js +0 -240
  229. package/dist/client/processors/media/AudioProcessor.js +0 -707
  230. package/dist/client/processors/media/VideoProcessor.js +0 -1045
  231. package/dist/client/providers/amazonBedrock.js +0 -1512
  232. package/dist/client/providers/amazonSagemaker.js +0 -162
  233. package/dist/client/providers/anthropic.js +0 -831
  234. package/dist/client/providers/azureOpenai.js +0 -143
  235. package/dist/client/providers/googleAiStudio.js +0 -1200
  236. package/dist/client/providers/googleNativeGemini3.js +0 -543
  237. package/dist/client/providers/googleVertex.js +0 -2936
  238. package/dist/client/providers/huggingFace.js +0 -315
  239. package/dist/client/providers/litellm.js +0 -488
  240. package/dist/client/providers/mistral.js +0 -157
  241. package/dist/client/providers/ollama.js +0 -1579
  242. package/dist/client/providers/openAI.js +0 -627
  243. package/dist/client/providers/openRouter.js +0 -543
  244. package/dist/client/providers/openaiCompatible.js +0 -290
  245. package/dist/client/providers/providerTypeUtils.js +0 -46
  246. package/dist/client/providers/sagemaker/adaptive-semaphore.js +0 -215
  247. package/dist/client/providers/sagemaker/client.js +0 -472
  248. package/dist/client/providers/sagemaker/config.js +0 -317
  249. package/dist/client/providers/sagemaker/detection.js +0 -606
  250. package/dist/client/providers/sagemaker/error-constants.js +0 -227
  251. package/dist/client/providers/sagemaker/errors.js +0 -299
  252. package/dist/client/providers/sagemaker/language-model.js +0 -775
  253. package/dist/client/providers/sagemaker/parsers.js +0 -634
  254. package/dist/client/providers/sagemaker/streaming.js +0 -331
  255. package/dist/client/providers/sagemaker/structured-parser.js +0 -625
  256. package/dist/client/proxy/accountQuota.js +0 -162
  257. package/dist/client/proxy/claudeFormat.js +0 -595
  258. package/dist/client/proxy/modelRouter.js +0 -29
  259. package/dist/client/proxy/oauthFetch.js +0 -367
  260. package/dist/client/proxy/proxyFetch.js +0 -586
  261. package/dist/client/proxy/requestLogger.js +0 -207
  262. package/dist/client/proxy/tokenRefresh.js +0 -124
  263. package/dist/client/proxy/usageStats.js +0 -74
  264. package/dist/client/proxy/utils/noProxyUtils.js +0 -149
  265. package/dist/client/rag/ChunkerFactory.js +0 -320
  266. package/dist/client/rag/ChunkerRegistry.js +0 -421
  267. package/dist/client/rag/chunkers/BaseChunker.js +0 -143
  268. package/dist/client/rag/chunkers/CharacterChunker.js +0 -28
  269. package/dist/client/rag/chunkers/HTMLChunker.js +0 -38
  270. package/dist/client/rag/chunkers/JSONChunker.js +0 -68
  271. package/dist/client/rag/chunkers/LaTeXChunker.js +0 -63
  272. package/dist/client/rag/chunkers/MarkdownChunker.js +0 -306
  273. package/dist/client/rag/chunkers/RecursiveChunker.js +0 -139
  274. package/dist/client/rag/chunkers/SemanticMarkdownChunker.js +0 -138
  275. package/dist/client/rag/chunkers/SentenceChunker.js +0 -66
  276. package/dist/client/rag/chunkers/TokenChunker.js +0 -61
  277. package/dist/client/rag/chunkers/index.js +0 -15
  278. package/dist/client/rag/chunking/characterChunker.js +0 -142
  279. package/dist/client/rag/chunking/chunkerRegistry.js +0 -194
  280. package/dist/client/rag/chunking/htmlChunker.js +0 -247
  281. package/dist/client/rag/chunking/index.js +0 -17
  282. package/dist/client/rag/chunking/jsonChunker.js +0 -281
  283. package/dist/client/rag/chunking/latexChunker.js +0 -251
  284. package/dist/client/rag/chunking/markdownChunker.js +0 -373
  285. package/dist/client/rag/chunking/recursiveChunker.js +0 -148
  286. package/dist/client/rag/chunking/semanticChunker.js +0 -306
  287. package/dist/client/rag/chunking/sentenceChunker.js +0 -230
  288. package/dist/client/rag/chunking/tokenChunker.js +0 -183
  289. package/dist/client/rag/document/MDocument.js +0 -392
  290. package/dist/client/rag/document/index.js +0 -5
  291. package/dist/client/rag/document/loaders.js +0 -500
  292. package/dist/client/rag/errors/RAGError.js +0 -274
  293. package/dist/client/rag/errors/index.js +0 -6
  294. package/dist/client/rag/graphRag/graphRAG.js +0 -401
  295. package/dist/client/rag/graphRag/index.js +0 -4
  296. package/dist/client/rag/index.js +0 -141
  297. package/dist/client/rag/metadata/MetadataExtractorFactory.js +0 -418
  298. package/dist/client/rag/metadata/MetadataExtractorRegistry.js +0 -362
  299. package/dist/client/rag/metadata/index.js +0 -9
  300. package/dist/client/rag/metadata/metadataExtractor.js +0 -280
  301. package/dist/client/rag/pipeline/RAGPipeline.js +0 -436
  302. package/dist/client/rag/pipeline/contextAssembly.js +0 -341
  303. package/dist/client/rag/pipeline/index.js +0 -5
  304. package/dist/client/rag/ragIntegration.js +0 -321
  305. package/dist/client/rag/reranker/RerankerFactory.js +0 -430
  306. package/dist/client/rag/reranker/RerankerRegistry.js +0 -402
  307. package/dist/client/rag/reranker/index.js +0 -9
  308. package/dist/client/rag/reranker/reranker.js +0 -277
  309. package/dist/client/rag/resilience/CircuitBreaker.js +0 -431
  310. package/dist/client/rag/resilience/RetryHandler.js +0 -304
  311. package/dist/client/rag/resilience/index.js +0 -7
  312. package/dist/client/rag/retrieval/hybridSearch.js +0 -335
  313. package/dist/client/rag/retrieval/index.js +0 -5
  314. package/dist/client/rag/retrieval/vectorQueryTool.js +0 -307
  315. package/dist/client/rag/types.js +0 -8
  316. package/dist/client/sdk/toolRegistration.js +0 -377
  317. package/dist/client/server/abstract/baseServerAdapter.js +0 -575
  318. package/dist/client/server/adapters/expressAdapter.js +0 -486
  319. package/dist/client/server/adapters/fastifyAdapter.js +0 -472
  320. package/dist/client/server/adapters/honoAdapter.js +0 -632
  321. package/dist/client/server/adapters/koaAdapter.js +0 -510
  322. package/dist/client/server/errors.js +0 -486
  323. package/dist/client/server/factory/serverAdapterFactory.js +0 -160
  324. package/dist/client/server/index.js +0 -108
  325. package/dist/client/server/middleware/abortSignal.js +0 -111
  326. package/dist/client/server/middleware/auth.js +0 -388
  327. package/dist/client/server/middleware/cache.js +0 -359
  328. package/dist/client/server/middleware/common.js +0 -281
  329. package/dist/client/server/middleware/deprecation.js +0 -190
  330. package/dist/client/server/middleware/mcpBodyAttachment.js +0 -63
  331. package/dist/client/server/middleware/rateLimit.js +0 -227
  332. package/dist/client/server/middleware/validation.js +0 -388
  333. package/dist/client/server/openapi/generator.js +0 -398
  334. package/dist/client/server/openapi/index.js +0 -36
  335. package/dist/client/server/openapi/schemas.js +0 -695
  336. package/dist/client/server/openapi/templates.js +0 -374
  337. package/dist/client/server/routes/agentRoutes.js +0 -189
  338. package/dist/client/server/routes/claudeProxyRoutes.js +0 -1600
  339. package/dist/client/server/routes/healthRoutes.js +0 -187
  340. package/dist/client/server/routes/index.js +0 -57
  341. package/dist/client/server/routes/mcpRoutes.js +0 -342
  342. package/dist/client/server/routes/memoryRoutes.js +0 -350
  343. package/dist/client/server/routes/openApiRoutes.js +0 -126
  344. package/dist/client/server/routes/toolRoutes.js +0 -199
  345. package/dist/client/server/streaming/dataStream.js +0 -486
  346. package/dist/client/server/streaming/index.js +0 -11
  347. package/dist/client/server/types.js +0 -67
  348. package/dist/client/server/utils/redaction.js +0 -334
  349. package/dist/client/server/utils/validation.js +0 -243
  350. package/dist/client/server/websocket/WebSocketHandler.js +0 -383
  351. package/dist/client/server/websocket/index.js +0 -4
  352. package/dist/client/services/server/ai/observability/instrumentation.js +0 -808
  353. package/dist/client/telemetry/attributes.js +0 -100
  354. package/dist/client/telemetry/index.js +0 -26
  355. package/dist/client/telemetry/telemetryService.js +0 -308
  356. package/dist/client/telemetry/tracers.js +0 -17
  357. package/dist/client/telemetry/withSpan.js +0 -34
  358. package/dist/client/types/actionTypes.js +0 -6
  359. package/dist/client/types/analytics.js +0 -5
  360. package/dist/client/types/authTypes.js +0 -9
  361. package/dist/client/types/circuitBreakerErrors.js +0 -34
  362. package/dist/client/types/cli.js +0 -21
  363. package/dist/client/types/clientTypes.js +0 -10
  364. package/dist/client/types/common.js +0 -51
  365. package/dist/client/types/configTypes.js +0 -49
  366. package/dist/client/types/content.js +0 -19
  367. package/dist/client/types/contextTypes.js +0 -400
  368. package/dist/client/types/conversation.js +0 -47
  369. package/dist/client/types/conversationMemoryInterface.js +0 -6
  370. package/dist/client/types/domainTypes.js +0 -5
  371. package/dist/client/types/errors.js +0 -167
  372. package/dist/client/types/evaluation.js +0 -5
  373. package/dist/client/types/evaluationProviders.js +0 -5
  374. package/dist/client/types/evaluationTypes.js +0 -1
  375. package/dist/client/types/externalMcp.js +0 -6
  376. package/dist/client/types/fileReferenceTypes.js +0 -8
  377. package/dist/client/types/fileTypes.js +0 -4
  378. package/dist/client/types/generateTypes.js +0 -1
  379. package/dist/client/types/guardrails.js +0 -1
  380. package/dist/client/types/hitlTypes.js +0 -8
  381. package/dist/client/types/index.js +0 -57
  382. package/dist/client/types/mcpTypes.js +0 -5
  383. package/dist/client/types/middlewareTypes.js +0 -1
  384. package/dist/client/types/modelTypes.js +0 -30
  385. package/dist/client/types/multimodal.js +0 -135
  386. package/dist/client/types/observability.js +0 -6
  387. package/dist/client/types/pptTypes.js +0 -82
  388. package/dist/client/types/providers.js +0 -111
  389. package/dist/client/types/proxyTypes.js +0 -16
  390. package/dist/client/types/ragTypes.js +0 -7
  391. package/dist/client/types/sdkTypes.js +0 -8
  392. package/dist/client/types/serviceTypes.js +0 -5
  393. package/dist/client/types/streamTypes.js +0 -1
  394. package/dist/client/types/subscriptionTypes.js +0 -9
  395. package/dist/client/types/taskClassificationTypes.js +0 -5
  396. package/dist/client/types/tools.js +0 -24
  397. package/dist/client/types/ttsTypes.js +0 -57
  398. package/dist/client/types/typeAliases.js +0 -48
  399. package/dist/client/types/utilities.js +0 -4
  400. package/dist/client/types/workflowTypes.js +0 -30
  401. package/dist/client/utils/async/withTimeout.js +0 -98
  402. package/dist/client/utils/asyncMutex.js +0 -60
  403. package/dist/client/utils/conversationMemory.js +0 -431
  404. package/dist/client/utils/csvProcessor.js +0 -846
  405. package/dist/client/utils/errorHandling.js +0 -936
  406. package/dist/client/utils/evaluationUtils.js +0 -131
  407. package/dist/client/utils/factoryProcessing.js +0 -589
  408. package/dist/client/utils/fileDetector.js +0 -2161
  409. package/dist/client/utils/imageCache.js +0 -376
  410. package/dist/client/utils/imageProcessor.js +0 -704
  411. package/dist/client/utils/logger.js +0 -491
  412. package/dist/client/utils/mcpDefaults.js +0 -134
  413. package/dist/client/utils/messageBuilder.js +0 -1653
  414. package/dist/client/utils/modelAliasResolver.js +0 -54
  415. package/dist/client/utils/modelDetection.js +0 -80
  416. package/dist/client/utils/modelRouter.js +0 -292
  417. package/dist/client/utils/multimodalOptionsBuilder.js +0 -65
  418. package/dist/client/utils/observabilityHelpers.js +0 -47
  419. package/dist/client/utils/parameterValidation.js +0 -966
  420. package/dist/client/utils/pdfProcessor.js +0 -410
  421. package/dist/client/utils/performance.js +0 -222
  422. package/dist/client/utils/pricing.js +0 -340
  423. package/dist/client/utils/promptRedaction.js +0 -62
  424. package/dist/client/utils/providerConfig.js +0 -1009
  425. package/dist/client/utils/providerHealth.js +0 -1237
  426. package/dist/client/utils/providerRetry.js +0 -112
  427. package/dist/client/utils/providerUtils.js +0 -434
  428. package/dist/client/utils/rateLimiter.js +0 -200
  429. package/dist/client/utils/redis.js +0 -368
  430. package/dist/client/utils/retryHandler.js +0 -269
  431. package/dist/client/utils/retryability.js +0 -22
  432. package/dist/client/utils/sanitizers/svg.js +0 -481
  433. package/dist/client/utils/schemaConversion.js +0 -255
  434. package/dist/client/utils/taskClassificationUtils.js +0 -149
  435. package/dist/client/utils/taskClassifier.js +0 -94
  436. package/dist/client/utils/thinkingConfig.js +0 -104
  437. package/dist/client/utils/timeout.js +0 -359
  438. package/dist/client/utils/tokenEstimation.js +0 -142
  439. package/dist/client/utils/tokenLimits.js +0 -125
  440. package/dist/client/utils/tokenUtils.js +0 -239
  441. package/dist/client/utils/toolUtils.js +0 -75
  442. package/dist/client/utils/transformationUtils.js +0 -554
  443. package/dist/client/utils/ttsProcessor.js +0 -286
  444. package/dist/client/utils/typeUtils.js +0 -97
  445. package/dist/client/utils/videoAnalysisProcessor.js +0 -67
  446. package/dist/client/workflow/config.js +0 -398
  447. package/dist/client/workflow/core/ensembleExecutor.js +0 -407
  448. package/dist/client/workflow/core/judgeScorer.js +0 -544
  449. package/dist/client/workflow/core/responseConditioner.js +0 -225
  450. package/dist/client/workflow/core/types/conditionerTypes.js +0 -7
  451. package/dist/client/workflow/core/types/ensembleTypes.js +0 -7
  452. package/dist/client/workflow/core/types/index.js +0 -7
  453. package/dist/client/workflow/core/types/judgeTypes.js +0 -7
  454. package/dist/client/workflow/core/types/layerTypes.js +0 -7
  455. package/dist/client/workflow/core/types/registryTypes.js +0 -7
  456. package/dist/client/workflow/core/workflowRegistry.js +0 -304
  457. package/dist/client/workflow/core/workflowRunner.js +0 -586
  458. package/dist/client/workflow/index.js +0 -50
  459. package/dist/client/workflow/types.js +0 -9
  460. package/dist/client/workflow/utils/types/index.js +0 -7
  461. package/dist/client/workflow/utils/workflowMetrics.js +0 -311
  462. package/dist/client/workflow/utils/workflowValidation.js +0 -420
  463. package/dist/client/workflow/workflows/adaptiveWorkflow.js +0 -366
  464. package/dist/client/workflow/workflows/consensusWorkflow.js +0 -192
  465. package/dist/client/workflow/workflows/fallbackWorkflow.js +0 -225
  466. package/dist/client/workflow/workflows/multiJudgeWorkflow.js +0 -351
  467. /package/dist/client/{client/reactHooks.js → reactHooks.js} +0 -0
@@ -1,2383 +0,0 @@
1
- /**
2
- * Slide Renderers
3
- *
4
- * Standalone render functions for each slide type.
5
- * Extracted from SlideGenerator to keep functions under 300 lines.
6
- *
7
- * @module presentation/slideRenderers
8
- */
9
- import { logger } from "../../utils/logger.js";
10
- import { getBulletOptions, getSlideTypeFormatting } from "./constants.js";
11
- import { bufferToDataUrl, calculateFontSize, createFormattedTextProps, hasMarkdownFormatting, parseMarkdownText, validateImageBuffer, } from "./utils.js";
12
- // ============================================================================
13
- // LAYOUT POSITIONS
14
- // ============================================================================
15
- /**
16
- * Minimum gap between elements (in inches)
17
- */
18
- const MIN_GAP = 0.1;
19
- /**
20
- * Default text fit option for pptxgenjs
21
- * 'shrink' = Shrink text to fit within the container
22
- */
23
- const DEFAULT_TEXT_FIT = "shrink";
24
- export const LAYOUT_POSITIONS = {
25
- margin: { x: 0.5, y: 0.4 },
26
- title: { x: 0.5, y: 0.4, w: 9, h: 0.8 },
27
- subtitle: { x: 0.5, y: 1.4, w: 9, h: 0.5 },
28
- content: { x: 0.5, y: 1.4, w: 9, h: 3.8 },
29
- contentFull: { x: 0.5, y: 1.4, w: 9, h: 3.8 },
30
- contentLeft: { x: 0.5, y: 1.4, w: 4.2, h: 3.8 },
31
- contentRight: { x: 5.3, y: 1.4, w: 4.2, h: 3.8 },
32
- imageRight: { x: 5.3, y: 1.4, w: 4.2, h: 3.8 },
33
- imageLeft: { x: 0.5, y: 1.4, w: 4.2, h: 3.8 },
34
- imageFull: { x: 0, y: 0, w: 10, h: 5.625 },
35
- imageCentered: { x: 2, y: 1.2, w: 6, h: 3.6 },
36
- columnLeft: { x: 0.5, y: 1.4, w: 4.2, h: 3.8 },
37
- columnRight: { x: 5.3, y: 1.4, w: 4.2, h: 3.8 },
38
- col1: { x: 0.5, y: 1.4, w: 2.8, h: 3.8 },
39
- col2: { x: 3.5, y: 1.4, w: 2.8, h: 3.8 },
40
- col3: { x: 6.5, y: 1.4, w: 2.8, h: 3.8 },
41
- chart: { x: 0.5, y: 1.4, w: 9, h: 3.8 },
42
- statRow: { y: 2.2, h: 2.5 },
43
- footer: { x: 0.5, y: 5.2, w: 9, h: 0.3 },
44
- logo: {
45
- "top-left": { x: 0.3, y: 0.2 },
46
- "top-right": { x: 8.5, y: 0.2 },
47
- "bottom-left": { x: 0.3, y: 5.0 },
48
- "bottom-right": { x: 8.5, y: 5.0 },
49
- },
50
- quote: { x: 1, y: 1.5, w: 8, h: 2.5 },
51
- quoteAuthor: { x: 1, y: 4.2, w: 8, h: 0.5 },
52
- };
53
- /**
54
- * Map legend position from SlideContent values to pptxgenjs values
55
- */
56
- const LEGEND_POS_MAP = {
57
- top: "t",
58
- bottom: "b",
59
- left: "l",
60
- right: "r",
61
- };
62
- // ============================================================================
63
- // HELPER FUNCTIONS - ENHANCED BACKGROUNDS
64
- // ============================================================================
65
- /** Extract theme colors for background (strips # prefix) */
66
- function extractBackgroundColors(theme) {
67
- return {
68
- primary: theme.colors.primary.replace("#", ""),
69
- secondary: theme.colors.secondary.replace("#", ""),
70
- accent: theme.colors.accent.replace("#", ""),
71
- background: theme.colors.background.replace("#", ""),
72
- };
73
- }
74
- /** Blue to purple diagonal gradient effect */
75
- function addGradientBlueBackground(slide, colors) {
76
- const { primary, secondary } = colors;
77
- slide.addShape("rect", {
78
- x: 0,
79
- y: 0,
80
- w: "100%",
81
- h: "100%",
82
- fill: { color: "E8F4FD" },
83
- });
84
- slide.addShape("rect", {
85
- x: 0,
86
- y: 0,
87
- w: 6,
88
- h: 3,
89
- fill: { color: primary, transparency: 85 },
90
- });
91
- slide.addShape("rect", {
92
- x: 0,
93
- y: 0,
94
- w: 4,
95
- h: 2,
96
- fill: { color: primary, transparency: 75 },
97
- });
98
- slide.addShape("rect", {
99
- x: 5,
100
- y: 3,
101
- w: 5,
102
- h: 2.625,
103
- fill: { color: secondary, transparency: 85 },
104
- });
105
- slide.addShape("rect", {
106
- x: 7,
107
- y: 4,
108
- w: 3,
109
- h: 1.625,
110
- fill: { color: secondary, transparency: 75 },
111
- });
112
- }
113
- /** Professional dark blue to teal gradient */
114
- function addGradientCorporateBackground(slide, colors) {
115
- const { primary } = colors;
116
- slide.addShape("rect", {
117
- x: 0,
118
- y: 0,
119
- w: "100%",
120
- h: "100%",
121
- fill: { color: "F0F9FF" },
122
- });
123
- slide.addShape("rect", {
124
- x: 0,
125
- y: 0,
126
- w: 3,
127
- h: "100%",
128
- fill: { color: "1E3A5F", transparency: 92 },
129
- });
130
- slide.addShape("rect", {
131
- x: 0,
132
- y: 0,
133
- w: 1.5,
134
- h: "100%",
135
- fill: { color: "1E3A5F", transparency: 85 },
136
- });
137
- slide.addShape("rect", {
138
- x: 7,
139
- y: 0,
140
- w: 3,
141
- h: "100%",
142
- fill: { color: "2E7D32", transparency: 92 },
143
- });
144
- slide.addShape("rect", {
145
- x: 0,
146
- y: 0,
147
- w: "100%",
148
- h: 0.03,
149
- fill: { color: primary },
150
- });
151
- }
152
- /** Warm orange to pink gradient */
153
- function addGradientWarmBackground(slide) {
154
- slide.addShape("rect", {
155
- x: 0,
156
- y: 0,
157
- w: "100%",
158
- h: "100%",
159
- fill: { color: "FFF7ED" },
160
- });
161
- slide.addShape("rect", {
162
- x: 0,
163
- y: 0,
164
- w: "100%",
165
- h: 2.5,
166
- fill: { color: "EA580C", transparency: 90 },
167
- });
168
- slide.addShape("rect", {
169
- x: 0,
170
- y: 0,
171
- w: "100%",
172
- h: 1,
173
- fill: { color: "EA580C", transparency: 80 },
174
- });
175
- slide.addShape("rect", {
176
- x: 0,
177
- y: 4.5,
178
- w: "100%",
179
- h: 1.125,
180
- fill: { color: "DB2777", transparency: 88 },
181
- });
182
- }
183
- /** Dark theme with accent glow */
184
- function addGradientDarkBackground(slide) {
185
- slide.addShape("rect", {
186
- x: 0,
187
- y: 0,
188
- w: "100%",
189
- h: "100%",
190
- fill: { color: "0F172A" },
191
- });
192
- slide.addShape("ellipse", {
193
- x: -2,
194
- y: -1,
195
- w: 6,
196
- h: 4,
197
- fill: { color: "06B6D4", transparency: 85 },
198
- });
199
- slide.addShape("ellipse", {
200
- x: 7,
201
- y: 3,
202
- w: 5,
203
- h: 4,
204
- fill: { color: "A855F7", transparency: 85 },
205
- });
206
- }
207
- /** Very subtle professional gradient */
208
- function addGradientSubtleBackground(slide, colors) {
209
- const { primary, secondary } = colors;
210
- slide.addShape("rect", {
211
- x: 0,
212
- y: 0,
213
- w: "100%",
214
- h: "100%",
215
- fill: { color: "FAFBFC" },
216
- });
217
- slide.addShape("rect", {
218
- x: 0,
219
- y: 0,
220
- w: "100%",
221
- h: 2,
222
- fill: { color: primary, transparency: 95 },
223
- });
224
- slide.addShape("rect", {
225
- x: 0,
226
- y: 4,
227
- w: "100%",
228
- h: 1.625,
229
- fill: { color: secondary, transparency: 96 },
230
- });
231
- slide.addShape("rect", {
232
- x: 0,
233
- y: 0,
234
- w: "100%",
235
- h: 0.02,
236
- fill: { color: primary },
237
- });
238
- }
239
- /** Geometric shapes pattern */
240
- function addGeometricBackground(slide, colors) {
241
- const { primary, secondary, accent, background } = colors;
242
- slide.addShape("rect", {
243
- x: 0,
244
- y: 0,
245
- w: "100%",
246
- h: "100%",
247
- fill: { color: background },
248
- });
249
- slide.addShape("rtTriangle", {
250
- x: 6,
251
- y: 3,
252
- w: 4,
253
- h: 2.625,
254
- fill: { color: primary, transparency: 90 },
255
- rotate: 180,
256
- });
257
- slide.addShape("rtTriangle", {
258
- x: 0,
259
- y: 0,
260
- w: 2.5,
261
- h: 2,
262
- fill: { color: secondary, transparency: 92 },
263
- });
264
- slide.addShape("ellipse", {
265
- x: 8.5,
266
- y: 0.2,
267
- w: 1,
268
- h: 1,
269
- fill: { color: accent, transparency: 85 },
270
- });
271
- slide.addShape("ellipse", {
272
- x: 0.5,
273
- y: 4.5,
274
- w: 0.6,
275
- h: 0.6,
276
- fill: { color: primary, transparency: 80 },
277
- });
278
- }
279
- /** Large corner accent shapes */
280
- function addCornerAccentBackground(slide, colors) {
281
- const { primary, secondary, background } = colors;
282
- slide.addShape("rect", {
283
- x: 0,
284
- y: 0,
285
- w: "100%",
286
- h: "100%",
287
- fill: { color: background },
288
- });
289
- slide.addShape("rect", {
290
- x: 7,
291
- y: 0,
292
- w: 3,
293
- h: 1.8,
294
- fill: { color: primary, transparency: 88 },
295
- });
296
- slide.addShape("rect", {
297
- x: 8.5,
298
- y: 0,
299
- w: 1.5,
300
- h: 1,
301
- fill: { color: primary, transparency: 70 },
302
- });
303
- slide.addShape("rect", {
304
- x: 0,
305
- y: 4,
306
- w: 2.5,
307
- h: 1.625,
308
- fill: { color: secondary, transparency: 88 },
309
- });
310
- slide.addShape("rect", {
311
- x: 0,
312
- y: 4.8,
313
- w: 1.2,
314
- h: 0.825,
315
- fill: { color: secondary, transparency: 70 },
316
- });
317
- }
318
- /** Curved wave pattern effect */
319
- function addWaveBackground(slide, colors) {
320
- const { primary, secondary, accent } = colors;
321
- slide.addShape("rect", {
322
- x: 0,
323
- y: 0,
324
- w: "100%",
325
- h: "100%",
326
- fill: { color: "F8FAFC" },
327
- });
328
- slide.addShape("ellipse", {
329
- x: -3,
330
- y: 4,
331
- w: 8,
332
- h: 3,
333
- fill: { color: primary, transparency: 92 },
334
- });
335
- slide.addShape("ellipse", {
336
- x: 2,
337
- y: 4.5,
338
- w: 7,
339
- h: 2.5,
340
- fill: { color: secondary, transparency: 93 },
341
- });
342
- slide.addShape("ellipse", {
343
- x: 6,
344
- y: 4.2,
345
- w: 6,
346
- h: 3,
347
- fill: { color: accent, transparency: 94 },
348
- });
349
- slide.addShape("rect", {
350
- x: 0,
351
- y: 0,
352
- w: "100%",
353
- h: 0.03,
354
- fill: { color: primary },
355
- });
356
- }
357
- /** Split diagonal background */
358
- function addSplitBackground(slide, colors) {
359
- const { primary, secondary, background } = colors;
360
- slide.addShape("rect", {
361
- x: 0,
362
- y: 0,
363
- w: "100%",
364
- h: "100%",
365
- fill: { color: background },
366
- });
367
- slide.addShape("rect", {
368
- x: 5,
369
- y: -1,
370
- w: 6,
371
- h: 8,
372
- fill: { color: primary, transparency: 94 },
373
- rotate: 15,
374
- });
375
- slide.addShape("rect", {
376
- x: 7,
377
- y: -1,
378
- w: 5,
379
- h: 8,
380
- fill: { color: secondary, transparency: 92 },
381
- rotate: 15,
382
- });
383
- }
384
- /** Simple solid with subtle accent */
385
- function addSolidBackground(slide, colors) {
386
- const { primary, background } = colors;
387
- slide.addShape("rect", {
388
- x: 0,
389
- y: 0,
390
- w: "100%",
391
- h: "100%",
392
- fill: { color: background },
393
- });
394
- slide.addShape("rect", {
395
- x: 0,
396
- y: 0,
397
- w: "100%",
398
- h: 0.02,
399
- fill: { color: primary },
400
- });
401
- }
402
- /**
403
- * Add enhanced background with gradient or multi-color designs
404
- * Creates visually appealing slides with sophisticated styling
405
- */
406
- export function addEnhancedBackground(slide, theme, style = "gradient-subtle") {
407
- const colors = extractBackgroundColors(theme);
408
- switch (style) {
409
- case "gradient-blue":
410
- addGradientBlueBackground(slide, colors);
411
- break;
412
- case "gradient-corporate":
413
- addGradientCorporateBackground(slide, colors);
414
- break;
415
- case "gradient-warm":
416
- addGradientWarmBackground(slide);
417
- break;
418
- case "gradient-dark":
419
- addGradientDarkBackground(slide);
420
- break;
421
- case "gradient-subtle":
422
- addGradientSubtleBackground(slide, colors);
423
- break;
424
- case "geometric":
425
- addGeometricBackground(slide, colors);
426
- break;
427
- case "corner-accent":
428
- addCornerAccentBackground(slide, colors);
429
- break;
430
- case "wave":
431
- addWaveBackground(slide, colors);
432
- break;
433
- case "split":
434
- addSplitBackground(slide, colors);
435
- break;
436
- case "solid":
437
- default:
438
- addSolidBackground(slide, colors);
439
- break;
440
- }
441
- }
442
- /**
443
- * Add subtle colored background to slides (legacy - use addEnhancedBackground for more options)
444
- */
445
- export function addColoredBackground(slide, theme, opacity = 0.05) {
446
- const primaryColor = theme.colors.primary.replace("#", "");
447
- slide.addShape("rect", {
448
- x: 0,
449
- y: 0,
450
- w: "100%",
451
- h: "100%",
452
- fill: {
453
- color: primaryColor,
454
- transparency: (1 - opacity) * 100, // pptx transparency is 0-100 where 100 is fully transparent
455
- },
456
- });
457
- }
458
- /**
459
- * Add modern card-style container with border
460
- */
461
- export function addCardContainer(slide, pos, theme, borderWidth = 2) {
462
- // Card background with subtle border
463
- slide.addShape("rect", {
464
- x: pos.x,
465
- y: pos.y,
466
- w: pos.w,
467
- h: pos.h,
468
- fill: {
469
- color: theme.colors.background.replace("#", ""),
470
- transparency: 0, // Fully opaque
471
- },
472
- line: {
473
- color: theme.colors.muted.replace("#", ""),
474
- width: borderWidth,
475
- },
476
- });
477
- }
478
- /**
479
- * Add accent bar for visual hierarchy
480
- */
481
- export function addAccentBar(slide, pos, theme, position = "left") {
482
- const barConfig = {
483
- left: { x: pos.x, y: pos.y, w: 0.1, h: pos.h },
484
- top: { x: pos.x, y: pos.y, w: pos.w, h: 0.1 },
485
- bottom: { x: pos.x, y: pos.y + pos.h - 0.1, w: pos.w, h: 0.1 },
486
- };
487
- slide.addShape("rect", {
488
- ...barConfig[position],
489
- fill: { color: theme.colors.primary.replace("#", "") },
490
- });
491
- }
492
- /**
493
- * Calculate text width in inches based on font size and character count.
494
- * Uses typographic metrics for Arial font family.
495
- *
496
- * @param text - The text string to measure
497
- * @param fontSize - Font size in points
498
- * @param isBold - Whether the text is bold (adds ~10% width)
499
- * @returns Width in inches
500
- */
501
- export function calculateTextWidth(text, fontSize, isBold = false) {
502
- // For proportional fonts like Arial:
503
- // - Average character width is approximately 0.42 * em-height for normal text
504
- // - 1 point = 1/72 inch, so em-height = fontSize / 72 inches
505
- // - Bold text is approximately 10% wider
506
- const emHeight = fontSize / 72; // em-height in inches
507
- const avgCharWidthRatio = 0.42; // Average char width as ratio of em-height (reduced for tighter fit)
508
- const boldMultiplier = isBold ? 1.1 : 1.0;
509
- const charWidth = emHeight * avgCharWidthRatio * boldMultiplier;
510
- return text.length * charWidth;
511
- }
512
- export function addTitle(slide, title, theme, showUnderline = true) {
513
- slide.addText(title, {
514
- x: LAYOUT_POSITIONS.title.x,
515
- y: LAYOUT_POSITIONS.title.y,
516
- w: LAYOUT_POSITIONS.title.w,
517
- h: LAYOUT_POSITIONS.title.h,
518
- fontSize: theme.fonts.sizes.heading,
519
- fontFace: theme.fonts.heading,
520
- color: theme.colors.text.replace("#", ""),
521
- bold: true,
522
- fit: DEFAULT_TEXT_FIT,
523
- });
524
- if (showUnderline) {
525
- // Calculate dynamic underline width based on actual title text and font size
526
- const textWidth = calculateTextWidth(title, theme.fonts.sizes.heading, true);
527
- // Constrain to reasonable bounds (min 1.5", max fits within slide margins)
528
- const maxWidth = LAYOUT_POSITIONS.title.w; // 9 inches - same as title container
529
- const minWidth = 1.0;
530
- const calculatedWidth = Math.min(maxWidth, Math.max(minWidth, textWidth));
531
- slide.addShape("rect", {
532
- x: LAYOUT_POSITIONS.title.x,
533
- y: LAYOUT_POSITIONS.title.y + LAYOUT_POSITIONS.title.h + 0.1,
534
- w: calculatedWidth,
535
- h: 0.03,
536
- fill: { color: theme.colors.primary.replace("#", "") },
537
- });
538
- }
539
- }
540
- /**
541
- * Add individual bullet items (each as separate text element)
542
- * This creates cleaner spacing and no bounding box around bullets
543
- * Useful for column layouts and when you want more control over spacing
544
- */
545
- export function addIndividualBullets(options) {
546
- const { slide, bullets, startX, startY, width, theme, itemSpacing = 0.45, } = options;
547
- if (!bullets || bullets.length === 0) {
548
- return;
549
- }
550
- // Ensure minimum gap between bullets
551
- const effectiveSpacing = Math.max(itemSpacing, MIN_GAP * 2);
552
- bullets.forEach((bullet, index) => {
553
- // Skip invalid bullets
554
- if (!bullet || !bullet.text) {
555
- return;
556
- }
557
- const isBold = bullet.bold || bullet.emphasis || false;
558
- const color = bullet.color?.replace("#", "") || theme.colors.text.replace("#", "");
559
- // Check if bullet text contains markdown formatting
560
- if (hasMarkdownFormatting(bullet.text)) {
561
- // Parse markdown and create rich text runs with bullet prefix
562
- const segments = parseMarkdownText(bullet.text);
563
- const textRuns = [
564
- {
565
- text: "• ",
566
- options: {
567
- fontSize: theme.fonts.sizes.body,
568
- fontFace: theme.fonts.body,
569
- color,
570
- },
571
- },
572
- ...createFormattedTextProps(segments, {
573
- fontSize: theme.fonts.sizes.body,
574
- fontFace: theme.fonts.body,
575
- color,
576
- baseBold: isBold,
577
- }),
578
- ];
579
- slide.addText(textRuns, {
580
- x: startX,
581
- y: startY + index * effectiveSpacing,
582
- w: width,
583
- h: Math.max(0.4, effectiveSpacing - MIN_GAP),
584
- fit: DEFAULT_TEXT_FIT,
585
- });
586
- }
587
- else {
588
- // No markdown - use simple text
589
- const bulletText = `• ${bullet.text}`;
590
- slide.addText(bulletText, {
591
- x: startX,
592
- y: startY + index * effectiveSpacing,
593
- w: width,
594
- h: Math.max(0.4, effectiveSpacing - MIN_GAP),
595
- fontSize: theme.fonts.sizes.body,
596
- fontFace: theme.fonts.body,
597
- color,
598
- bold: isBold,
599
- fit: DEFAULT_TEXT_FIT,
600
- });
601
- }
602
- });
603
- }
604
- /**
605
- * Add bullets to a slide with hybrid formatting
606
- *
607
- * Priority: bullet-level > slide-level > type-defaults > theme-defaults
608
- *
609
- * @param slide - The pptxgenjs slide
610
- * @param bullets - Array of bullet points (normalized)
611
- * @param pos - Position and dimensions
612
- * @param theme - Presentation theme
613
- * @param slideType - Slide type for default formatting (optional)
614
- */
615
- export function addBullets(slide, bullets, pos, theme, slideType = "content", options) {
616
- if (!bullets || bullets.length === 0) {
617
- return;
618
- }
619
- // Use individual bullets for cleaner spacing (no bounding box)
620
- if (options?.useIndividualBullets) {
621
- addIndividualBullets({
622
- slide,
623
- bullets,
624
- startX: pos.x,
625
- startY: pos.y,
626
- width: pos.w,
627
- theme,
628
- itemSpacing: options.itemSpacing ?? 0.45,
629
- });
630
- return;
631
- }
632
- // Get hardcoded defaults for this slide type
633
- const typeDefaults = getSlideTypeFormatting(slideType);
634
- // Calculate base font size based on bullet count (if not overridden)
635
- const calculatedFontSize = calculateFontSize(bullets.length, typeDefaults.baseFontSize || theme.fonts.sizes.body);
636
- const textLines = [];
637
- bullets.forEach((bullet) => {
638
- // Skip invalid bullets
639
- if (!bullet || !bullet.text) {
640
- return;
641
- }
642
- // Priority: bullet-level > type-defaults
643
- const bulletStyle = bullet.bulletStyle || typeDefaults.bulletStyle || "disc";
644
- const fontSize = bullet.fontSize || calculatedFontSize;
645
- const color = bullet.color?.replace("#", "") || theme.colors.text.replace("#", "");
646
- const isBold = bullet.bold ?? bullet.emphasis ?? false;
647
- // Get pptxgenjs bullet options based on style
648
- // If bullet has custom icon, use that instead of style
649
- let bulletOptions;
650
- if (bullet.icon) {
651
- bulletOptions = { code: bullet.icon };
652
- }
653
- else {
654
- bulletOptions = getBulletOptions(bulletStyle);
655
- }
656
- // Check if bullet text contains markdown formatting (**bold**, *italic*)
657
- if (hasMarkdownFormatting(bullet.text)) {
658
- // Parse markdown and create rich text runs
659
- const segments = parseMarkdownText(bullet.text);
660
- const textRuns = createFormattedTextProps(segments, {
661
- fontSize,
662
- fontFace: theme.fonts.body,
663
- color,
664
- baseBold: isBold,
665
- });
666
- textLines.push({
667
- text: textRuns,
668
- options: {
669
- bullet: bulletOptions,
670
- indentLevel: 0,
671
- paraSpaceBefore: 6,
672
- paraSpaceAfter: 6,
673
- },
674
- });
675
- }
676
- else {
677
- // No markdown - use simple text
678
- textLines.push({
679
- text: bullet.text,
680
- options: {
681
- bullet: bulletOptions,
682
- fontSize,
683
- fontFace: theme.fonts.body,
684
- color,
685
- bold: isBold,
686
- indentLevel: 0,
687
- paraSpaceBefore: 6, // Add space before each paragraph (in points)
688
- paraSpaceAfter: 6, // Add space after each paragraph (in points)
689
- },
690
- });
691
- }
692
- // Handle sub-bullets (2pt smaller than main bullet)
693
- if (bullet.subBullets && bullet.subBullets.length > 0) {
694
- bullet.subBullets.forEach((subBullet) => {
695
- // Check for markdown in sub-bullets too
696
- if (hasMarkdownFormatting(subBullet)) {
697
- const segments = parseMarkdownText(subBullet);
698
- const textRuns = createFormattedTextProps(segments, {
699
- fontSize: Math.max(10, fontSize - 2),
700
- fontFace: theme.fonts.body,
701
- color: theme.colors.muted.replace("#", ""),
702
- });
703
- textLines.push({
704
- text: textRuns,
705
- options: {
706
- bullet: true,
707
- indentLevel: 1,
708
- paraSpaceBefore: 3,
709
- paraSpaceAfter: 3,
710
- },
711
- });
712
- }
713
- else {
714
- textLines.push({
715
- text: subBullet,
716
- options: {
717
- bullet: true,
718
- fontSize: Math.max(10, fontSize - 2),
719
- fontFace: theme.fonts.body,
720
- color: theme.colors.muted.replace("#", ""),
721
- indentLevel: 1,
722
- paraSpaceBefore: 3,
723
- paraSpaceAfter: 3,
724
- },
725
- });
726
- }
727
- });
728
- }
729
- });
730
- slide.addText(textLines, {
731
- x: pos.x,
732
- y: pos.y,
733
- w: pos.w,
734
- h: pos.h,
735
- valign: "top",
736
- fit: DEFAULT_TEXT_FIT,
737
- });
738
- }
739
- export function addImage(slide, imageBuffer, pos) {
740
- // Validate and get data URL
741
- const validation = validateImageBuffer(imageBuffer);
742
- if (!validation.isValid && validation.format === "") {
743
- logger.warn("[addImage] Invalid image buffer", { error: validation.error });
744
- return;
745
- }
746
- if (!validation.isValid) {
747
- logger.warn("[addImage] Image validation warning, attempting to add anyway", {
748
- error: validation.error,
749
- mimeType: validation.mimeType,
750
- });
751
- }
752
- const dataUrl = bufferToDataUrl(imageBuffer);
753
- if (!dataUrl) {
754
- logger.warn("[addImage] Failed to convert buffer to data URL");
755
- return;
756
- }
757
- try {
758
- slide.addImage({
759
- data: dataUrl,
760
- x: pos.x,
761
- y: pos.y,
762
- w: pos.w,
763
- h: pos.h,
764
- sizing: { type: "cover", w: pos.w, h: pos.h },
765
- });
766
- }
767
- catch (error) {
768
- logger.error("[addImage] Failed to add image to slide", {
769
- error: error instanceof Error ? error.message : String(error),
770
- });
771
- }
772
- }
773
- export function getPptxChartType(slideType) {
774
- switch (slideType) {
775
- case "chart-bar":
776
- return "bar";
777
- case "chart-line":
778
- return "line";
779
- case "chart-pie":
780
- return "pie";
781
- case "chart-area":
782
- return "area";
783
- default:
784
- return "bar";
785
- }
786
- }
787
- // ============================================================================
788
- // SLIDE RENDERERS - OPENING/CLOSING
789
- // ============================================================================
790
- export function renderTitleSlide(slide, title, content, theme, imageBuffer) {
791
- const layout = content.layoutOptions || {};
792
- const titleOpts = layout.title || {};
793
- const subtitleOpts = layout.subtitle || {};
794
- const bgOpts = layout.background || {};
795
- // Background: image > custom color > theme default (white)
796
- const imageDataUrl = imageBuffer ? bufferToDataUrl(imageBuffer) : null;
797
- if (imageDataUrl) {
798
- slide.background = {
799
- data: imageDataUrl,
800
- };
801
- slide.addShape("rect", {
802
- x: 0,
803
- y: 0,
804
- w: "100%",
805
- h: "100%",
806
- fill: { color: "000000", transparency: 40 },
807
- });
808
- }
809
- else if (bgOpts.color ||
810
- bgOpts.useThemePrimary ||
811
- bgOpts.useThemeSecondary) {
812
- const bgColor = bgOpts.useThemePrimary
813
- ? theme.colors.primary
814
- : bgOpts.useThemeSecondary
815
- ? theme.colors.secondary
816
- : bgOpts.color || theme.colors.background;
817
- slide.addShape("rect", {
818
- x: 0,
819
- y: 0,
820
- w: 10,
821
- h: 5.625,
822
- fill: { color: bgColor.replace("#", "") },
823
- });
824
- }
825
- // Determine text colors based on background
826
- const hasColoredBg = imageBuffer ||
827
- bgOpts.color ||
828
- bgOpts.useThemePrimary ||
829
- bgOpts.useThemeSecondary;
830
- const defaultTitleColor = hasColoredBg
831
- ? "FFFFFF"
832
- : theme.colors.text.replace("#", "");
833
- const defaultSubtitleColor = hasColoredBg
834
- ? "FFFFFF"
835
- : theme.colors.muted.replace("#", "");
836
- // Title
837
- slide.addText(title, {
838
- x: titleOpts.x ?? 0.5,
839
- y: titleOpts.y ?? 2,
840
- w: titleOpts.w ?? 9,
841
- h: titleOpts.h ?? 1.2,
842
- fontSize: titleOpts.fontSize ?? theme.fonts.sizes.title,
843
- fontFace: theme.fonts.heading,
844
- color: titleOpts.color?.replace("#", "") ?? defaultTitleColor,
845
- align: titleOpts.align ?? "center",
846
- bold: true,
847
- fit: DEFAULT_TEXT_FIT,
848
- });
849
- // Subtitle
850
- if (content.subtitle) {
851
- slide.addText(content.subtitle, {
852
- x: subtitleOpts.x ?? 0.5,
853
- y: subtitleOpts.y ?? 3.3,
854
- w: subtitleOpts.w ?? 9,
855
- h: subtitleOpts.h ?? 0.6,
856
- fontSize: subtitleOpts.fontSize ?? theme.fonts.sizes.subtitle,
857
- fontFace: theme.fonts.body,
858
- color: subtitleOpts.color?.replace("#", "") ?? defaultSubtitleColor,
859
- align: subtitleOpts.align ?? "center",
860
- fit: DEFAULT_TEXT_FIT,
861
- });
862
- }
863
- }
864
- export function renderSectionHeaderSlide(slide, title, content, theme) {
865
- const layout = content.layoutOptions || {};
866
- const sectionNumOpts = layout.sectionNumber || {};
867
- const titleOpts = layout.title || {};
868
- const subtitleOpts = layout.subtitle || {};
869
- const bgOpts = layout.background || {};
870
- // Apply background if specified
871
- if (bgOpts.color) {
872
- slide.addShape("rect", {
873
- x: 0,
874
- y: 0,
875
- w: 10,
876
- h: 5.625,
877
- fill: { color: bgOpts.color.replace("#", "") },
878
- });
879
- }
880
- // Section number
881
- if (content.sectionNumber) {
882
- const isWatermark = sectionNumOpts.style === "watermark";
883
- const isSmall = sectionNumOpts.style === "small";
884
- slide.addText(String(content.sectionNumber).padStart(2, "0"), {
885
- x: sectionNumOpts.x ?? (isWatermark ? 5.5 : 0.5),
886
- y: sectionNumOpts.y ?? (isWatermark ? 0.5 : 1.5),
887
- w: isWatermark ? 5 : 2,
888
- h: isWatermark ? 4 : 1,
889
- fontSize: sectionNumOpts.fontSize ?? (isWatermark ? 200 : isSmall ? 32 : 72),
890
- fontFace: theme.fonts.heading,
891
- color: theme.colors.primary.replace("#", ""),
892
- bold: true,
893
- align: isWatermark ? "right" : "left",
894
- transparency: isWatermark ? 70 : 0,
895
- });
896
- }
897
- // Title
898
- slide.addText(title, {
899
- x: titleOpts.x ?? 0.5,
900
- y: titleOpts.y ?? 2.5,
901
- w: titleOpts.w ?? 9,
902
- h: titleOpts.h ?? 1,
903
- fontSize: titleOpts.fontSize ?? theme.fonts.sizes.title,
904
- fontFace: theme.fonts.heading,
905
- color: theme.colors.text.replace("#", ""),
906
- bold: true,
907
- align: titleOpts.align ?? "left",
908
- });
909
- // Subtitle (if provided)
910
- if (content.subtitle) {
911
- slide.addText(content.subtitle, {
912
- x: subtitleOpts.x ?? 0.5,
913
- y: subtitleOpts.y ?? 3.6,
914
- w: subtitleOpts.w ?? 8,
915
- h: subtitleOpts.h ?? 0.6,
916
- fontSize: subtitleOpts.fontSize ?? theme.fonts.sizes.subtitle,
917
- fontFace: theme.fonts.body,
918
- color: theme.colors.muted.replace("#", ""),
919
- align: subtitleOpts.align ?? "left",
920
- });
921
- }
922
- }
923
- export function renderThankYouSlide(slide, title, content, theme, imageBuffer) {
924
- if (imageBuffer) {
925
- const dataUrl = bufferToDataUrl(imageBuffer);
926
- if (dataUrl) {
927
- slide.background = {
928
- data: dataUrl,
929
- };
930
- slide.addShape("rect", {
931
- x: 0,
932
- y: 0,
933
- w: "100%",
934
- h: "100%",
935
- fill: { color: "000000", transparency: 40 },
936
- });
937
- }
938
- }
939
- const textColor = imageBuffer ? "FFFFFF" : theme.colors.text.replace("#", "");
940
- slide.addText(title || "Thank You!", {
941
- x: 0.5,
942
- y: 1.8,
943
- w: 9,
944
- h: 1,
945
- fontSize: theme.fonts.sizes.title,
946
- fontFace: theme.fonts.heading,
947
- color: textColor,
948
- align: "center",
949
- bold: true,
950
- fit: DEFAULT_TEXT_FIT,
951
- });
952
- if (content.cta) {
953
- slide.addText(content.cta, {
954
- x: 0.5,
955
- y: 2.9,
956
- w: 9,
957
- h: 0.5,
958
- fontSize: theme.fonts.sizes.subtitle,
959
- fontFace: theme.fonts.body,
960
- color: imageBuffer ? "FFFFFF" : theme.colors.muted.replace("#", ""),
961
- align: "center",
962
- fit: DEFAULT_TEXT_FIT,
963
- });
964
- }
965
- if (content.contactInfo) {
966
- const contactLines = [];
967
- if (content.contactInfo.email) {
968
- contactLines.push(`Email: ${content.contactInfo.email}`);
969
- }
970
- if (content.contactInfo.website) {
971
- contactLines.push(`Web: ${content.contactInfo.website}`);
972
- }
973
- if (content.contactInfo.phone) {
974
- contactLines.push(`Phone: ${content.contactInfo.phone}`);
975
- }
976
- if (contactLines.length > 0) {
977
- slide.addText(contactLines.join(" • "), {
978
- x: 0.5,
979
- y: 4.2,
980
- w: 9,
981
- h: 0.4,
982
- fontSize: theme.fonts.sizes.body,
983
- fontFace: theme.fonts.body,
984
- color: textColor,
985
- align: "center",
986
- fit: DEFAULT_TEXT_FIT,
987
- });
988
- }
989
- if (content.contactInfo.social &&
990
- Array.isArray(content.contactInfo.social) &&
991
- content.contactInfo.social.length > 0) {
992
- const socialText = content.contactInfo.social
993
- .map((s) => `${s.platform || ""}: ${s.handle || ""}`)
994
- .join(" • ");
995
- slide.addText(socialText, {
996
- x: 0.5,
997
- y: 4.7,
998
- w: 9,
999
- h: 0.3,
1000
- fontSize: theme.fonts.sizes.caption,
1001
- fontFace: theme.fonts.body,
1002
- color: imageBuffer ? "CCCCCC" : theme.colors.muted.replace("#", ""),
1003
- align: "center",
1004
- });
1005
- }
1006
- }
1007
- }
1008
- // ============================================================================
1009
- // SLIDE RENDERERS - CONTENT
1010
- // ============================================================================
1011
- export function renderContentSlide(options) {
1012
- const { slide, title, content, layout, theme, imageBuffer, slideType = "content", } = options;
1013
- addTitle(slide, title, theme);
1014
- const hasImage = imageBuffer && layout.includes("image");
1015
- const contentPos = hasImage
1016
- ? layout.includes("left")
1017
- ? LAYOUT_POSITIONS.contentRight
1018
- : LAYOUT_POSITIONS.contentLeft
1019
- : LAYOUT_POSITIONS.contentFull;
1020
- if (content.bullets && content.bullets.length > 0) {
1021
- addBullets(slide, content.bullets, contentPos, theme, slideType);
1022
- }
1023
- else if (content.body) {
1024
- slide.addText(content.body, {
1025
- x: contentPos.x,
1026
- y: contentPos.y,
1027
- w: contentPos.w,
1028
- h: contentPos.h,
1029
- fontSize: theme.fonts.sizes.body,
1030
- fontFace: theme.fonts.body,
1031
- color: theme.colors.text.replace("#", ""),
1032
- valign: "top",
1033
- });
1034
- }
1035
- if (imageBuffer && hasImage) {
1036
- const imagePos = layout.includes("left")
1037
- ? LAYOUT_POSITIONS.imageLeft
1038
- : LAYOUT_POSITIONS.imageRight;
1039
- addImage(slide, imageBuffer, imagePos);
1040
- }
1041
- }
1042
- export function renderImageSlide(slide, title, content, layout, theme, imageBuffer) {
1043
- if (layout === "image-full-overlay" && imageBuffer) {
1044
- const dataUrl = bufferToDataUrl(imageBuffer);
1045
- if (dataUrl) {
1046
- slide.background = {
1047
- data: dataUrl,
1048
- };
1049
- slide.addShape("rect", {
1050
- x: 0,
1051
- y: 0,
1052
- w: "100%",
1053
- h: "100%",
1054
- fill: { color: "000000", transparency: 50 },
1055
- });
1056
- slide.addText(title, {
1057
- x: 0.5,
1058
- y: 4.2,
1059
- w: 9,
1060
- h: 1,
1061
- fontSize: theme.fonts.sizes.heading,
1062
- fontFace: theme.fonts.heading,
1063
- color: "FFFFFF",
1064
- bold: true,
1065
- fit: DEFAULT_TEXT_FIT,
1066
- });
1067
- }
1068
- else {
1069
- // Fallback to standard layout if image is invalid
1070
- addTitle(slide, title, theme);
1071
- }
1072
- }
1073
- else if (layout === "image-centered" || layout === "image-full-overlay") {
1074
- addTitle(slide, title, theme);
1075
- if (imageBuffer) {
1076
- addImage(slide, imageBuffer, LAYOUT_POSITIONS.imageCentered);
1077
- }
1078
- else {
1079
- // Fallback when no image is available - show placeholder with content
1080
- slide.addShape("rect", {
1081
- x: 1.5,
1082
- y: 1.8,
1083
- w: 7,
1084
- h: 3.5,
1085
- fill: { color: theme.colors.muted.replace("#", ""), transparency: 90 },
1086
- line: {
1087
- color: theme.colors.muted.replace("#", ""),
1088
- width: 1,
1089
- dashType: "dash",
1090
- },
1091
- });
1092
- slide.addText("📷", {
1093
- x: 1.5,
1094
- y: 2.8,
1095
- w: 7,
1096
- h: 1,
1097
- fontSize: 48,
1098
- align: "center",
1099
- valign: "middle",
1100
- fit: DEFAULT_TEXT_FIT,
1101
- });
1102
- slide.addText("Image not available", {
1103
- x: 1.5,
1104
- y: 3.8,
1105
- w: 7,
1106
- h: 0.5,
1107
- fontSize: 12,
1108
- fontFace: theme.fonts.body,
1109
- color: theme.colors.muted.replace("#", ""),
1110
- align: "center",
1111
- fit: DEFAULT_TEXT_FIT,
1112
- });
1113
- }
1114
- if (content.caption) {
1115
- slide.addText(content.caption, {
1116
- x: 0.5,
1117
- y: 5,
1118
- w: 9,
1119
- h: 0.4,
1120
- fontSize: theme.fonts.sizes.caption,
1121
- fontFace: theme.fonts.body,
1122
- color: theme.colors.muted.replace("#", ""),
1123
- align: "center",
1124
- fit: DEFAULT_TEXT_FIT,
1125
- });
1126
- }
1127
- }
1128
- else {
1129
- renderContentSlide({ slide, title, content, layout, theme, imageBuffer });
1130
- }
1131
- }
1132
- // ============================================================================
1133
- // SLIDE RENDERERS - COLUMNS
1134
- // ============================================================================
1135
- // ============================================================================
1136
- // SLIDE RENDERERS - COLUMNS (Generic)
1137
- // ============================================================================
1138
- /**
1139
- * Generic column slide renderer
1140
- * Handles 2, 3, or more columns dynamically
1141
- * Renders each bullet as separate element (comparison-style)
1142
- */
1143
- export function renderColumnSlide(slide, title, columns, theme, options) {
1144
- addTitle(slide, title, theme, false);
1145
- const opts = {
1146
- headerY: options?.headerY ?? 1.3,
1147
- headerHeight: options?.headerHeight ?? 0.5,
1148
- bulletsStartY: options?.bulletsStartY ?? 2.0,
1149
- columnGap: options?.columnGap ?? 0.3,
1150
- highlightFirstColumn: options?.highlightFirstColumn ?? true,
1151
- };
1152
- const numColumns = columns.length;
1153
- if (numColumns === 0) {
1154
- return;
1155
- }
1156
- // Calculate dynamic column widths
1157
- const totalWidth = 9; // Available width (10 - 0.5 margin on each side)
1158
- const totalGaps = (numColumns - 1) * opts.columnGap;
1159
- const columnWidth = (totalWidth - totalGaps) / numColumns;
1160
- const startX = 0.5;
1161
- columns.forEach((col, index) => {
1162
- if (!col) {
1163
- return;
1164
- }
1165
- const x = startX + index * (columnWidth + opts.columnGap);
1166
- const isPrimary = opts.highlightFirstColumn && index === 0;
1167
- // Render header box if title exists
1168
- if (col.title) {
1169
- slide.addShape("roundRect", {
1170
- x,
1171
- y: opts.headerY,
1172
- w: columnWidth,
1173
- h: opts.headerHeight,
1174
- fill: {
1175
- color: isPrimary
1176
- ? theme.colors.primary.replace("#", "")
1177
- : theme.colors.muted.replace("#", ""),
1178
- },
1179
- rectRadius: 0.05,
1180
- });
1181
- // Adjust font size based on column count
1182
- const headerFontSize = numColumns <= 2 ? theme.fonts.sizes.body : theme.fonts.sizes.body - 2;
1183
- slide.addText(col.title, {
1184
- x,
1185
- y: opts.headerY,
1186
- w: columnWidth,
1187
- h: opts.headerHeight,
1188
- fontSize: headerFontSize,
1189
- fontFace: theme.fonts.heading,
1190
- color: isPrimary
1191
- ? theme.colors.textOnPrimary.replace("#", "")
1192
- : theme.colors.text.replace("#", ""),
1193
- align: "center",
1194
- bold: true,
1195
- valign: "middle",
1196
- fit: DEFAULT_TEXT_FIT,
1197
- });
1198
- }
1199
- // Render bullets individually (like comparison slide)
1200
- if (col.bullets && col.bullets.length > 0) {
1201
- addIndividualBullets({
1202
- slide,
1203
- bullets: col.bullets,
1204
- startX: x,
1205
- startY: opts.bulletsStartY,
1206
- width: columnWidth,
1207
- theme,
1208
- });
1209
- }
1210
- });
1211
- }
1212
- export function renderTwoColumnSlide(slide, title, content, layout, theme, imageBuffer) {
1213
- const columns = [];
1214
- if (content.leftColumn) {
1215
- columns.push(content.leftColumn);
1216
- }
1217
- if (content.rightColumn) {
1218
- columns.push(content.rightColumn);
1219
- }
1220
- if (columns.length > 0) {
1221
- renderColumnSlide(slide, title, columns, theme, {
1222
- highlightFirstColumn: true,
1223
- });
1224
- }
1225
- // Handle image if right column has no bullets
1226
- if (imageBuffer && !content.rightColumn?.bullets) {
1227
- addImage(slide, imageBuffer, LAYOUT_POSITIONS.columnRight);
1228
- }
1229
- }
1230
- export function renderThreeColumnSlide(slide, title, content, theme) {
1231
- const columns = [];
1232
- if (content.leftColumn) {
1233
- columns.push(content.leftColumn);
1234
- }
1235
- if (content.centerColumn) {
1236
- columns.push(content.centerColumn);
1237
- }
1238
- if (content.rightColumn) {
1239
- columns.push(content.rightColumn);
1240
- }
1241
- if (columns.length > 0) {
1242
- renderColumnSlide(slide, title, columns, theme, {
1243
- highlightFirstColumn: true,
1244
- });
1245
- }
1246
- }
1247
- // ============================================================================
1248
- // SLIDE RENDERERS - DATA VISUALIZATION
1249
- // ============================================================================
1250
- export function renderQuoteSlide(slide, title, content, theme) {
1251
- slide.addText("\u201C", {
1252
- x: 0.5,
1253
- y: 1,
1254
- w: 1,
1255
- h: 1,
1256
- fontSize: 120,
1257
- fontFace: "Georgia",
1258
- color: theme.colors.primary.replace("#", ""),
1259
- fit: DEFAULT_TEXT_FIT,
1260
- });
1261
- if (content.quote) {
1262
- slide.addText(content.quote, {
1263
- x: LAYOUT_POSITIONS.quote.x,
1264
- y: LAYOUT_POSITIONS.quote.y,
1265
- w: LAYOUT_POSITIONS.quote.w,
1266
- h: LAYOUT_POSITIONS.quote.h,
1267
- fontSize: theme.fonts.sizes.heading,
1268
- fontFace: "Georgia",
1269
- color: theme.colors.text.replace("#", ""),
1270
- italic: true,
1271
- valign: "middle",
1272
- fit: DEFAULT_TEXT_FIT,
1273
- });
1274
- }
1275
- if (content.quoteAuthor) {
1276
- let authorText = `— ${content.quoteAuthor}`;
1277
- if (content.quoteAuthorTitle) {
1278
- authorText += `, ${content.quoteAuthorTitle}`;
1279
- }
1280
- slide.addText(authorText, {
1281
- x: LAYOUT_POSITIONS.quoteAuthor.x,
1282
- y: LAYOUT_POSITIONS.quoteAuthor.y,
1283
- w: LAYOUT_POSITIONS.quoteAuthor.w,
1284
- h: LAYOUT_POSITIONS.quoteAuthor.h,
1285
- fontSize: theme.fonts.sizes.body,
1286
- fontFace: theme.fonts.body,
1287
- color: theme.colors.muted.replace("#", ""),
1288
- align: "right",
1289
- fit: DEFAULT_TEXT_FIT,
1290
- });
1291
- }
1292
- }
1293
- export function renderStatisticsSlide(slide, title, content, theme) {
1294
- addTitle(slide, title, theme);
1295
- if (!content.statistics || content.statistics.length === 0) {
1296
- return;
1297
- }
1298
- const stats = content.statistics.slice(0, 4);
1299
- const statWidth = 9 / stats.length;
1300
- stats.forEach((stat, index) => {
1301
- const x = 0.5 + index * statWidth;
1302
- slide.addText(stat.value, {
1303
- x,
1304
- y: LAYOUT_POSITIONS.statRow.y,
1305
- w: statWidth - 0.2,
1306
- h: 1.2,
1307
- fontSize: 48,
1308
- fontFace: theme.fonts.heading,
1309
- color: theme.colors.primary.replace("#", ""),
1310
- bold: true,
1311
- align: "center",
1312
- fit: DEFAULT_TEXT_FIT,
1313
- });
1314
- slide.addText(stat.label, {
1315
- x,
1316
- y: LAYOUT_POSITIONS.statRow.y + 1.3,
1317
- w: statWidth - 0.2,
1318
- h: 0.5,
1319
- fontSize: theme.fonts.sizes.body,
1320
- fontFace: theme.fonts.body,
1321
- color: theme.colors.text.replace("#", ""),
1322
- align: "center",
1323
- fit: DEFAULT_TEXT_FIT,
1324
- });
1325
- if (stat.change || stat.trend) {
1326
- const trendColor = stat.trend === "up"
1327
- ? "22C55E"
1328
- : stat.trend === "down"
1329
- ? "EF4444"
1330
- : theme.colors.muted.replace("#", "");
1331
- // Use simple text indicators instead of Unicode arrows for better compatibility
1332
- const trendSymbol = stat.trend === "up" ? "(+)" : stat.trend === "down" ? "(-)" : "";
1333
- slide.addText(`${trendSymbol} ${stat.change || ""}`, {
1334
- x,
1335
- y: LAYOUT_POSITIONS.statRow.y + 1.8,
1336
- w: statWidth - 0.2,
1337
- h: 0.4,
1338
- fontSize: theme.fonts.sizes.caption,
1339
- fontFace: theme.fonts.body,
1340
- color: trendColor,
1341
- align: "center",
1342
- fit: DEFAULT_TEXT_FIT,
1343
- });
1344
- }
1345
- });
1346
- }
1347
- export function renderChartSlide(slide, title, content, chartType, theme) {
1348
- addTitle(slide, title, theme);
1349
- // Validate chartData exists with valid series
1350
- if (!content.chartData ||
1351
- !content.chartData.series ||
1352
- content.chartData.series.length === 0) {
1353
- // Add placeholder text for empty chart
1354
- slide.addText("No chart data available", {
1355
- x: LAYOUT_POSITIONS.chart.x,
1356
- y: LAYOUT_POSITIONS.chart.y + 1,
1357
- w: LAYOUT_POSITIONS.chart.w,
1358
- h: 1,
1359
- fontSize: 18,
1360
- color: theme.colors.muted.replace("#", ""),
1361
- align: "center",
1362
- fit: DEFAULT_TEXT_FIT,
1363
- });
1364
- return;
1365
- }
1366
- // Filter out invalid series (must have name, labels array, and values array)
1367
- const validSeries = content.chartData.series.filter((series) => series.name &&
1368
- Array.isArray(series.labels) &&
1369
- series.labels.length > 0 &&
1370
- Array.isArray(series.values) &&
1371
- series.values.length > 0);
1372
- if (validSeries.length === 0) {
1373
- slide.addText("Invalid chart data format", {
1374
- x: LAYOUT_POSITIONS.chart.x,
1375
- y: LAYOUT_POSITIONS.chart.y + 1,
1376
- w: LAYOUT_POSITIONS.chart.w,
1377
- h: 1,
1378
- fontSize: 18,
1379
- color: theme.colors.muted.replace("#", ""),
1380
- align: "center",
1381
- fit: DEFAULT_TEXT_FIT,
1382
- });
1383
- return;
1384
- }
1385
- const pptxChartType = getPptxChartType(chartType);
1386
- const chartData = validSeries.map((series) => ({
1387
- name: series.name,
1388
- labels: series.labels,
1389
- values: series.values,
1390
- }));
1391
- slide.addChart(pptxChartType, chartData, {
1392
- x: LAYOUT_POSITIONS.chart.x,
1393
- y: LAYOUT_POSITIONS.chart.y,
1394
- w: LAYOUT_POSITIONS.chart.w,
1395
- h: LAYOUT_POSITIONS.chart.h,
1396
- showTitle: !!content.chartData.title,
1397
- title: content.chartData.title,
1398
- showLegend: content.chartData.legendPosition !== "none",
1399
- legendPos: LEGEND_POS_MAP[content.chartData.legendPosition || "bottom"] || "b",
1400
- showValue: content.chartData.showLabels,
1401
- chartColors: [
1402
- theme.colors.primary.replace("#", ""),
1403
- theme.colors.secondary.replace("#", ""),
1404
- theme.colors.accent.replace("#", ""),
1405
- ],
1406
- });
1407
- }
1408
- export function renderTableSlide(slide, title, content, theme) {
1409
- addTitle(slide, title, theme);
1410
- if (!content.tableData) {
1411
- return;
1412
- }
1413
- const { headers, rows, hasHeader } = content.tableData;
1414
- const tableRows = [];
1415
- if (hasHeader && headers) {
1416
- tableRows.push(headers.map((header) => ({
1417
- text: header,
1418
- options: {
1419
- bold: true,
1420
- fill: { color: theme.colors.primary.replace("#", "") },
1421
- color: theme.colors.textOnPrimary.replace("#", ""),
1422
- align: "center",
1423
- },
1424
- })));
1425
- }
1426
- rows.forEach((row, rowIndex) => {
1427
- tableRows.push(row.map((cell) => ({
1428
- text: cell.text,
1429
- options: {
1430
- fill: { color: rowIndex % 2 === 0 ? "F8FAFC" : "FFFFFF" },
1431
- color: theme.colors.text.replace("#", ""),
1432
- align: (cell.align || "left"),
1433
- },
1434
- })));
1435
- });
1436
- slide.addTable(tableRows, {
1437
- x: LAYOUT_POSITIONS.chart.x,
1438
- y: LAYOUT_POSITIONS.chart.y,
1439
- w: LAYOUT_POSITIONS.chart.w,
1440
- colW: Array(headers?.length || rows[0]?.length || 1).fill(LAYOUT_POSITIONS.chart.w / (headers?.length || rows[0]?.length || 1)),
1441
- fontSize: theme.fonts.sizes.body - 2,
1442
- fontFace: theme.fonts.body,
1443
- border: { pt: 0.5, color: "E2E8F0" },
1444
- autoPage: true,
1445
- });
1446
- if (content.tableData.caption) {
1447
- slide.addText(content.tableData.caption, {
1448
- x: 0.5,
1449
- y: 5.1,
1450
- w: 9,
1451
- h: 0.3,
1452
- fontSize: theme.fonts.sizes.caption,
1453
- fontFace: theme.fonts.body,
1454
- color: theme.colors.muted.replace("#", ""),
1455
- align: "center",
1456
- });
1457
- }
1458
- }
1459
- // ============================================================================
1460
- // SLIDE RENDERERS - PROCESS & TIMELINE
1461
- // ============================================================================
1462
- export function renderTimelineSlide(slide, title, content, theme) {
1463
- addTitle(slide, title, theme);
1464
- if (!content.timeline?.items) {
1465
- return;
1466
- }
1467
- const items = content.timeline.items.slice(0, 5);
1468
- const isHorizontal = content.timeline.orientation !== "vertical";
1469
- if (isHorizontal) {
1470
- renderHorizontalTimeline(slide, items, theme);
1471
- }
1472
- else {
1473
- renderVerticalTimeline(slide, items, theme);
1474
- }
1475
- }
1476
- function renderHorizontalTimeline(slide, items, theme) {
1477
- const itemWidth = 8 / items.length;
1478
- const lineY = 2.8;
1479
- slide.addShape("rect", {
1480
- x: 1,
1481
- y: lineY,
1482
- w: 8,
1483
- h: 0.02,
1484
- fill: { color: theme.colors.primary.replace("#", "") },
1485
- });
1486
- items.forEach((item, index) => {
1487
- const x = 1 + index * itemWidth + itemWidth / 2 - 0.15;
1488
- slide.addShape("ellipse", {
1489
- x,
1490
- y: lineY - 0.15,
1491
- w: 0.3,
1492
- h: 0.3,
1493
- fill: { color: theme.colors.primary.replace("#", "") },
1494
- });
1495
- slide.addText(item.date, {
1496
- x: x - itemWidth / 2 + 0.15,
1497
- y: lineY - 0.8,
1498
- w: itemWidth,
1499
- h: 0.4,
1500
- fontSize: theme.fonts.sizes.caption,
1501
- fontFace: theme.fonts.body,
1502
- color: theme.colors.primary.replace("#", ""),
1503
- align: "center",
1504
- bold: true,
1505
- fit: DEFAULT_TEXT_FIT,
1506
- });
1507
- slide.addText(item.title, {
1508
- x: x - itemWidth / 2 + 0.15,
1509
- y: lineY + 0.4,
1510
- w: itemWidth,
1511
- h: 0.4,
1512
- fontSize: theme.fonts.sizes.body,
1513
- fontFace: theme.fonts.heading,
1514
- color: theme.colors.text.replace("#", ""),
1515
- align: "center",
1516
- bold: true,
1517
- fit: DEFAULT_TEXT_FIT,
1518
- });
1519
- if (item.description) {
1520
- slide.addText(item.description, {
1521
- x: x - itemWidth / 2 + 0.15,
1522
- y: lineY + 0.85,
1523
- w: itemWidth,
1524
- h: 0.8,
1525
- fontSize: theme.fonts.sizes.caption,
1526
- fontFace: theme.fonts.body,
1527
- color: theme.colors.muted.replace("#", ""),
1528
- align: "center",
1529
- fit: DEFAULT_TEXT_FIT,
1530
- });
1531
- }
1532
- });
1533
- }
1534
- function renderVerticalTimeline(slide, items, theme) {
1535
- const itemHeight = 3 / items.length;
1536
- const lineX = 1.5;
1537
- slide.addShape("rect", {
1538
- x: lineX,
1539
- y: 1.5,
1540
- w: 0.04,
1541
- h: 3.5,
1542
- fill: { color: theme.colors.primary.replace("#", "") },
1543
- });
1544
- items.forEach((item, index) => {
1545
- const y = 1.7 + index * itemHeight;
1546
- slide.addShape("ellipse", {
1547
- x: lineX - 0.12,
1548
- y,
1549
- w: 0.28,
1550
- h: 0.28,
1551
- fill: { color: theme.colors.primary.replace("#", "") },
1552
- });
1553
- slide.addText(item.date, {
1554
- x: 0.3,
1555
- y: y - 0.1,
1556
- w: 1,
1557
- h: 0.3,
1558
- fontSize: theme.fonts.sizes.caption,
1559
- fontFace: theme.fonts.body,
1560
- color: theme.colors.primary.replace("#", ""),
1561
- bold: true,
1562
- fit: DEFAULT_TEXT_FIT,
1563
- });
1564
- slide.addText(item.title, {
1565
- x: 2,
1566
- y: y - 0.1,
1567
- w: 7,
1568
- h: 0.4,
1569
- fontSize: theme.fonts.sizes.body,
1570
- fontFace: theme.fonts.heading,
1571
- color: theme.colors.text.replace("#", ""),
1572
- bold: true,
1573
- fit: DEFAULT_TEXT_FIT,
1574
- });
1575
- if (item.description) {
1576
- slide.addText(item.description, {
1577
- x: 2,
1578
- y: y + 0.3,
1579
- w: 7,
1580
- h: 0.4,
1581
- fontSize: theme.fonts.sizes.caption,
1582
- fontFace: theme.fonts.body,
1583
- color: theme.colors.muted.replace("#", ""),
1584
- fit: DEFAULT_TEXT_FIT,
1585
- });
1586
- }
1587
- });
1588
- }
1589
- export function renderProcessFlowSlide(slide, title, content, theme) {
1590
- addTitle(slide, title, theme);
1591
- if (!content.processSteps) {
1592
- return;
1593
- }
1594
- const steps = content.processSteps.slice(0, 5);
1595
- const stepWidth = 8 / steps.length;
1596
- steps.forEach((step, index) => {
1597
- const x = 1 + index * stepWidth;
1598
- const boxWidth = stepWidth - 0.4;
1599
- slide.addShape("roundRect", {
1600
- x,
1601
- y: 2,
1602
- w: boxWidth,
1603
- h: 2,
1604
- fill: { color: theme.colors.primary.replace("#", "") },
1605
- rectRadius: 0.1,
1606
- });
1607
- slide.addText(String(step.step), {
1608
- x,
1609
- y: 2.1,
1610
- w: boxWidth,
1611
- h: 0.5,
1612
- fontSize: 24,
1613
- fontFace: theme.fonts.heading,
1614
- color: theme.colors.textOnPrimary.replace("#", ""),
1615
- align: "center",
1616
- bold: true,
1617
- fit: DEFAULT_TEXT_FIT,
1618
- });
1619
- slide.addText(step.title, {
1620
- x,
1621
- y: 2.6,
1622
- w: boxWidth,
1623
- h: 0.5,
1624
- fontSize: theme.fonts.sizes.body,
1625
- fontFace: theme.fonts.heading,
1626
- color: theme.colors.textOnPrimary.replace("#", ""),
1627
- align: "center",
1628
- bold: true,
1629
- fit: DEFAULT_TEXT_FIT,
1630
- });
1631
- if (step.description) {
1632
- slide.addText(step.description, {
1633
- x,
1634
- y: 3.2,
1635
- w: boxWidth,
1636
- h: 0.7,
1637
- fontSize: theme.fonts.sizes.caption,
1638
- fontFace: theme.fonts.body,
1639
- color: theme.colors.textOnPrimary.replace("#", ""),
1640
- align: "center",
1641
- fit: DEFAULT_TEXT_FIT,
1642
- });
1643
- }
1644
- if (index < steps.length - 1) {
1645
- // Use rightArrow shape for better visibility
1646
- slide.addShape("rightArrow", {
1647
- x: x + boxWidth + 0.02,
1648
- y: 2.85,
1649
- w: 0.35,
1650
- h: 0.3,
1651
- fill: { color: theme.colors.primary.replace("#", "") },
1652
- });
1653
- }
1654
- });
1655
- }
1656
- // ============================================================================
1657
- // SLIDE RENDERERS - COMPARISON & FEATURES
1658
- // ============================================================================
1659
- export function renderComparisonSlide(slide, title, content, theme) {
1660
- addTitle(slide, title, theme, false);
1661
- if (!content.comparison?.columns) {
1662
- return;
1663
- }
1664
- const columns = content.comparison.columns.slice(0, 2);
1665
- const columnWidth = 4.2;
1666
- // Calculate available height for bullet items (from y=2.0 to y=5.0 = 3.0 inches)
1667
- const contentStartY = 2.0;
1668
- const contentEndY = 5.0;
1669
- const availableHeight = contentEndY - contentStartY;
1670
- // Find max items across columns to calculate consistent spacing
1671
- const maxItems = Math.max(...columns.map((col) => Math.min(col.items?.length || 0, 6)));
1672
- // Calculate item height with minimum gap (at least 0.1 inch gap between items)
1673
- const itemHeight = maxItems > 0
1674
- ? Math.min(0.7, (availableHeight - MIN_GAP * maxItems) / maxItems)
1675
- : 0.5;
1676
- const effectiveSpacing = itemHeight + MIN_GAP;
1677
- columns.forEach((col, index) => {
1678
- const x = 0.5 + index * 4.8;
1679
- slide.addShape("roundRect", {
1680
- x,
1681
- y: 1.4,
1682
- w: columnWidth,
1683
- h: 0.5,
1684
- fill: {
1685
- color: col.highlight
1686
- ? theme.colors.primary.replace("#", "")
1687
- : theme.colors.muted.replace("#", ""),
1688
- },
1689
- rectRadius: 0.05,
1690
- });
1691
- slide.addText(col.title, {
1692
- x,
1693
- y: 1.4,
1694
- w: columnWidth,
1695
- h: 0.5,
1696
- fontSize: theme.fonts.sizes.body,
1697
- fontFace: theme.fonts.heading,
1698
- color: col.highlight
1699
- ? theme.colors.textOnPrimary.replace("#", "")
1700
- : theme.colors.text.replace("#", ""),
1701
- align: "center",
1702
- bold: true,
1703
- valign: "middle",
1704
- fit: DEFAULT_TEXT_FIT,
1705
- });
1706
- // Limit items to prevent overflow (max 6 items)
1707
- const items = (col.items || []).slice(0, 6);
1708
- // Calculate font size based on item count (smaller font for more items)
1709
- const fontSize = items.length > 4
1710
- ? Math.max(12, theme.fonts.sizes.body - 2)
1711
- : theme.fonts.sizes.body;
1712
- items.forEach((item, itemIndex) => {
1713
- // Check for markdown formatting
1714
- if (hasMarkdownFormatting(item)) {
1715
- const segments = parseMarkdownText(item);
1716
- const textRuns = [
1717
- {
1718
- text: "• ",
1719
- options: {
1720
- fontSize,
1721
- fontFace: theme.fonts.body,
1722
- color: theme.colors.text.replace("#", ""),
1723
- },
1724
- },
1725
- ...createFormattedTextProps(segments, {
1726
- fontSize,
1727
- fontFace: theme.fonts.body,
1728
- color: theme.colors.text.replace("#", ""),
1729
- }),
1730
- ];
1731
- slide.addText(textRuns, {
1732
- x: x + 0.2,
1733
- y: contentStartY + itemIndex * effectiveSpacing,
1734
- w: columnWidth - 0.4,
1735
- h: itemHeight,
1736
- valign: "top",
1737
- fit: DEFAULT_TEXT_FIT,
1738
- });
1739
- }
1740
- else {
1741
- slide.addText(`• ${item}`, {
1742
- x: x + 0.2,
1743
- y: contentStartY + itemIndex * effectiveSpacing,
1744
- w: columnWidth - 0.4,
1745
- h: itemHeight,
1746
- fontSize,
1747
- fontFace: theme.fonts.body,
1748
- color: theme.colors.text.replace("#", ""),
1749
- valign: "top",
1750
- fit: DEFAULT_TEXT_FIT,
1751
- });
1752
- }
1753
- });
1754
- });
1755
- }
1756
- export function renderFeaturesSlide(slide, title, content, theme) {
1757
- addTitle(slide, title, theme);
1758
- const rawFeatures = content.features || [];
1759
- const rawIcons = content.icons || [];
1760
- const normalizedFeatures = rawFeatures.length > 0
1761
- ? rawFeatures
1762
- : rawIcons.map((icon) => ({
1763
- title: icon.label,
1764
- description: icon.description || "",
1765
- icon: icon.icon,
1766
- }));
1767
- if (normalizedFeatures.length === 0) {
1768
- return;
1769
- }
1770
- const itemsPerRow = Math.min(normalizedFeatures.length, 3);
1771
- const itemWidth = 9 / itemsPerRow;
1772
- normalizedFeatures
1773
- .slice(0, 6)
1774
- .forEach((feature, index) => {
1775
- const row = Math.floor(index / itemsPerRow);
1776
- const col = index % itemsPerRow;
1777
- const x = 0.5 + col * itemWidth;
1778
- const y = 1.6 + row * 1.8;
1779
- if (feature.icon) {
1780
- const codePoint = parseInt(feature.icon, 16);
1781
- if (!Number.isNaN(codePoint) &&
1782
- codePoint >= 0 &&
1783
- codePoint <= 0x10ffff) {
1784
- const iconChar = String.fromCodePoint(codePoint);
1785
- slide.addText(iconChar, {
1786
- x,
1787
- y,
1788
- w: itemWidth - 0.2,
1789
- h: 0.6,
1790
- fontSize: 36,
1791
- fontFace: "Segoe UI Emoji",
1792
- color: theme.colors.primary.replace("#", ""),
1793
- align: "center",
1794
- fit: DEFAULT_TEXT_FIT,
1795
- });
1796
- }
1797
- }
1798
- slide.addText(feature.title, {
1799
- x,
1800
- y: y + 0.6,
1801
- w: itemWidth - 0.2,
1802
- h: 0.4,
1803
- fontSize: theme.fonts.sizes.body,
1804
- fontFace: theme.fonts.heading,
1805
- color: theme.colors.text.replace("#", ""),
1806
- align: "center",
1807
- bold: true,
1808
- fit: DEFAULT_TEXT_FIT,
1809
- });
1810
- if (feature.description) {
1811
- slide.addText(feature.description, {
1812
- x,
1813
- y: y + 1,
1814
- w: itemWidth - 0.2,
1815
- h: 0.6,
1816
- fontSize: theme.fonts.sizes.caption,
1817
- fontFace: theme.fonts.body,
1818
- color: theme.colors.muted.replace("#", ""),
1819
- align: "center",
1820
- fit: DEFAULT_TEXT_FIT,
1821
- });
1822
- }
1823
- });
1824
- }
1825
- export function renderTeamSlide(slide, title, content, theme) {
1826
- addTitle(slide, title, theme);
1827
- if (!content.teamMembers) {
1828
- return;
1829
- }
1830
- const members = content.teamMembers.slice(0, 4);
1831
- const memberWidth = 9 / members.length;
1832
- members.forEach((member, index) => {
1833
- const x = 0.5 + index * memberWidth;
1834
- slide.addShape("ellipse", {
1835
- x: x + (memberWidth - 1.5) / 2,
1836
- y: 1.6,
1837
- w: 1.5,
1838
- h: 1.5,
1839
- fill: { color: theme.colors.muted.replace("#", "") },
1840
- });
1841
- const initials = member.name
1842
- .split(" ")
1843
- .map((n) => n[0])
1844
- .join("")
1845
- .substring(0, 2);
1846
- slide.addText(initials, {
1847
- x: x + (memberWidth - 1.5) / 2,
1848
- y: 2,
1849
- w: 1.5,
1850
- h: 0.7,
1851
- fontSize: 28,
1852
- fontFace: theme.fonts.heading,
1853
- color: "FFFFFF",
1854
- align: "center",
1855
- bold: true,
1856
- fit: DEFAULT_TEXT_FIT,
1857
- });
1858
- slide.addText(member.name, {
1859
- x,
1860
- y: 3.3,
1861
- w: memberWidth - 0.2,
1862
- h: 0.4,
1863
- fontSize: theme.fonts.sizes.body,
1864
- fontFace: theme.fonts.heading,
1865
- color: theme.colors.text.replace("#", ""),
1866
- align: "center",
1867
- bold: true,
1868
- fit: DEFAULT_TEXT_FIT,
1869
- });
1870
- slide.addText(member.role, {
1871
- x,
1872
- y: 3.7,
1873
- w: memberWidth - 0.2,
1874
- h: 0.3,
1875
- fontSize: theme.fonts.sizes.caption,
1876
- fontFace: theme.fonts.body,
1877
- color: theme.colors.muted.replace("#", ""),
1878
- align: "center",
1879
- fit: DEFAULT_TEXT_FIT,
1880
- });
1881
- });
1882
- }
1883
- export function renderConclusionSlide(slide, title, content, theme) {
1884
- addTitle(slide, title, theme);
1885
- if (content.bullets) {
1886
- const checkmarkBullets = content.bullets.map((bullet) => ({
1887
- ...bullet,
1888
- icon: bullet.icon || "2713",
1889
- }));
1890
- addBullets(slide, checkmarkBullets, LAYOUT_POSITIONS.contentFull, theme, "conclusion");
1891
- }
1892
- if (content.cta) {
1893
- slide.addText(content.cta, {
1894
- x: 0.5,
1895
- y: 4.6,
1896
- w: 9,
1897
- h: 0.5,
1898
- fontSize: theme.fonts.sizes.heading - 4,
1899
- fontFace: theme.fonts.heading,
1900
- color: theme.colors.primary.replace("#", ""),
1901
- align: "center",
1902
- bold: true,
1903
- fit: DEFAULT_TEXT_FIT,
1904
- });
1905
- }
1906
- }
1907
- // ============================================================================
1908
- // COMPOSITE/DASHBOARD SLIDE RENDERERS
1909
- // Mixed content types on a single slide
1910
- // ============================================================================
1911
- /**
1912
- * Predefined grid layouts for composite slides
1913
- */
1914
- export const COMPOSITE_LAYOUTS = {
1915
- // Two halves side by side
1916
- "left-right": [
1917
- { x: 0.5, y: 1.4, w: 4.3, h: 3.6 },
1918
- { x: 5.2, y: 1.4, w: 4.3, h: 3.6 },
1919
- ],
1920
- // Top and bottom
1921
- "top-bottom": [
1922
- { x: 0.5, y: 1.2, w: 9, h: 1.8 },
1923
- { x: 0.5, y: 3.2, w: 9, h: 2.0 },
1924
- ],
1925
- // Three columns
1926
- "three-cols": [
1927
- { x: 0.5, y: 1.4, w: 2.8, h: 3.6 },
1928
- { x: 3.5, y: 1.4, w: 2.8, h: 3.6 },
1929
- { x: 6.5, y: 1.4, w: 2.8, h: 3.6 },
1930
- ],
1931
- // Four quadrants
1932
- quadrants: [
1933
- { x: 0.5, y: 1.2, w: 4.3, h: 1.8 },
1934
- { x: 5.2, y: 1.2, w: 4.3, h: 1.8 },
1935
- { x: 0.5, y: 3.2, w: 4.3, h: 1.8 },
1936
- { x: 5.2, y: 3.2, w: 4.3, h: 1.8 },
1937
- ],
1938
- // Five boxes (2 on top, 3 on bottom)
1939
- "five-boxes": [
1940
- { x: 0.5, y: 1.2, w: 4.3, h: 1.6 },
1941
- { x: 5.2, y: 1.2, w: 4.3, h: 1.6 },
1942
- { x: 0.5, y: 3.0, w: 2.8, h: 2.0 },
1943
- { x: 3.5, y: 3.0, w: 2.8, h: 2.0 },
1944
- { x: 6.5, y: 3.0, w: 2.8, h: 2.0 },
1945
- ],
1946
- // Six boxes (2x3 grid)
1947
- "six-boxes": [
1948
- { x: 0.5, y: 1.2, w: 2.8, h: 1.6 },
1949
- { x: 3.5, y: 1.2, w: 2.8, h: 1.6 },
1950
- { x: 6.5, y: 1.2, w: 2.8, h: 1.6 },
1951
- { x: 0.5, y: 3.0, w: 2.8, h: 1.8 },
1952
- { x: 3.5, y: 3.0, w: 2.8, h: 1.8 },
1953
- { x: 6.5, y: 3.0, w: 2.8, h: 1.8 },
1954
- ],
1955
- // Main content left, sidebar right
1956
- "main-sidebar": [
1957
- { x: 0.5, y: 1.4, w: 5.8, h: 3.6 },
1958
- { x: 6.5, y: 1.4, w: 3.0, h: 3.6 },
1959
- ],
1960
- // Wide top, three boxes bottom
1961
- "top-three": [
1962
- { x: 0.5, y: 1.2, w: 9, h: 1.6 },
1963
- { x: 0.5, y: 3.0, w: 2.8, h: 2.0 },
1964
- { x: 3.5, y: 3.0, w: 2.8, h: 2.0 },
1965
- { x: 6.5, y: 3.0, w: 2.8, h: 2.0 },
1966
- ],
1967
- };
1968
- /**
1969
- * Render a content box with background
1970
- */
1971
- function renderContentBox(slide, pos, title, theme, isPrimary = false) {
1972
- // Add background box
1973
- slide.addShape("roundRect", {
1974
- x: pos.x,
1975
- y: pos.y,
1976
- w: pos.w,
1977
- h: pos.h,
1978
- fill: {
1979
- color: isPrimary
1980
- ? theme.colors.primary.replace("#", "") + "15"
1981
- : "F8F9FA",
1982
- },
1983
- line: {
1984
- color: isPrimary ? theme.colors.primary.replace("#", "") : "E5E7EB",
1985
- width: 1,
1986
- },
1987
- rectRadius: 0.1,
1988
- });
1989
- let contentY = pos.y + 0.15;
1990
- let contentH = pos.h - 0.3;
1991
- // Add title if provided
1992
- if (title) {
1993
- slide.addText(title, {
1994
- x: pos.x + 0.15,
1995
- y: pos.y + 0.1,
1996
- w: pos.w - 0.3,
1997
- h: 0.35,
1998
- fontSize: 14,
1999
- fontFace: theme.fonts.heading,
2000
- color: isPrimary
2001
- ? theme.colors.primary.replace("#", "")
2002
- : theme.colors.text.replace("#", ""),
2003
- bold: true,
2004
- fit: DEFAULT_TEXT_FIT,
2005
- });
2006
- contentY = pos.y + 0.45;
2007
- contentH = pos.h - 0.55;
2008
- }
2009
- return { contentY, contentH };
2010
- }
2011
- /**
2012
- * Render bullets in a zone
2013
- */
2014
- function renderBulletsInZone(slide, bullets, pos, theme, contentY, contentH) {
2015
- if (!bullets || bullets.length === 0) {
2016
- return;
2017
- }
2018
- const fontSize = Math.max(11, Math.min(14, 16 - bullets.length));
2019
- const color = theme.colors.text.replace("#", "");
2020
- bullets.forEach((bullet, idx) => {
2021
- // Skip invalid bullets
2022
- if (!bullet || !bullet.text) {
2023
- return;
2024
- }
2025
- const bulletY = contentY + idx * (contentH / bullets.length);
2026
- // Check if bullet text contains markdown formatting
2027
- if (hasMarkdownFormatting(bullet.text)) {
2028
- const segments = parseMarkdownText(bullet.text);
2029
- const textRuns = [
2030
- {
2031
- text: "• ",
2032
- options: { fontSize, fontFace: theme.fonts.body, color },
2033
- },
2034
- ...createFormattedTextProps(segments, {
2035
- fontSize,
2036
- fontFace: theme.fonts.body,
2037
- color,
2038
- }),
2039
- ];
2040
- slide.addText(textRuns, {
2041
- x: pos.x + 0.15,
2042
- y: bulletY,
2043
- w: pos.w - 0.3,
2044
- h: contentH / bullets.length,
2045
- valign: "top",
2046
- fit: DEFAULT_TEXT_FIT,
2047
- });
2048
- }
2049
- else {
2050
- slide.addText(`• ${bullet.text}`, {
2051
- x: pos.x + 0.15,
2052
- y: bulletY,
2053
- w: pos.w - 0.3,
2054
- h: contentH / bullets.length,
2055
- fontSize,
2056
- fontFace: theme.fonts.body,
2057
- color,
2058
- valign: "top",
2059
- fit: DEFAULT_TEXT_FIT,
2060
- });
2061
- }
2062
- });
2063
- }
2064
- /**
2065
- * Render mini stat in a zone
2066
- */
2067
- function renderStatInZone(slide, stat, pos, theme, contentY, contentH) {
2068
- // Value
2069
- slide.addText(stat.value, {
2070
- x: pos.x,
2071
- y: contentY,
2072
- w: pos.w,
2073
- h: contentH * 0.6,
2074
- fontSize: Math.min(32, pos.w * 8),
2075
- fontFace: theme.fonts.heading,
2076
- color: theme.colors.primary.replace("#", ""),
2077
- bold: true,
2078
- align: "center",
2079
- valign: "bottom",
2080
- fit: DEFAULT_TEXT_FIT,
2081
- });
2082
- // Label
2083
- slide.addText(stat.label, {
2084
- x: pos.x,
2085
- y: contentY + contentH * 0.6,
2086
- w: pos.w,
2087
- h: contentH * 0.4,
2088
- fontSize: 11,
2089
- fontFace: theme.fonts.body,
2090
- color: theme.colors.muted.replace("#", ""),
2091
- align: "center",
2092
- valign: "top",
2093
- fit: DEFAULT_TEXT_FIT,
2094
- });
2095
- }
2096
- /**
2097
- * Render icon box in a zone
2098
- */
2099
- function renderIconBoxInZone(options) {
2100
- const { slide, icon, label, description, pos, theme, contentY, contentH } = options;
2101
- // Icon circle
2102
- const iconSize = Math.min(0.6, pos.w * 0.2);
2103
- slide.addShape("ellipse", {
2104
- x: pos.x + (pos.w - iconSize) / 2,
2105
- y: contentY,
2106
- w: iconSize,
2107
- h: iconSize,
2108
- fill: { color: theme.colors.primary.replace("#", "") },
2109
- });
2110
- // Icon text (Unicode)
2111
- const codePoint = parseInt(icon, 16);
2112
- if (Number.isNaN(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
2113
- logger.warn("[renderIconBoxInZone] Invalid icon code", { icon });
2114
- return;
2115
- }
2116
- const iconChar = String.fromCodePoint(codePoint);
2117
- slide.addText(iconChar, {
2118
- x: pos.x + (pos.w - iconSize) / 2,
2119
- y: contentY,
2120
- w: iconSize,
2121
- h: iconSize,
2122
- fontSize: 16,
2123
- color: "FFFFFF",
2124
- align: "center",
2125
- valign: "middle",
2126
- fit: DEFAULT_TEXT_FIT,
2127
- });
2128
- // Label
2129
- slide.addText(label, {
2130
- x: pos.x,
2131
- y: contentY + iconSize + 0.1,
2132
- w: pos.w,
2133
- h: 0.3,
2134
- fontSize: 12,
2135
- fontFace: theme.fonts.heading,
2136
- color: theme.colors.text.replace("#", ""),
2137
- bold: true,
2138
- align: "center",
2139
- fit: DEFAULT_TEXT_FIT,
2140
- });
2141
- // Description
2142
- if (description) {
2143
- slide.addText(description, {
2144
- x: pos.x + 0.1,
2145
- y: contentY + iconSize + 0.4,
2146
- w: pos.w - 0.2,
2147
- h: contentH - iconSize - 0.5,
2148
- fontSize: 10,
2149
- fontFace: theme.fonts.body,
2150
- color: theme.colors.muted.replace("#", ""),
2151
- align: "center",
2152
- fit: DEFAULT_TEXT_FIT,
2153
- });
2154
- }
2155
- }
2156
- /**
2157
- * Render a mini chart in a zone
2158
- */
2159
- function renderMiniChartInZone(slide, chartData, pos, theme, contentY, contentH) {
2160
- const chartType = chartData.type === "pie"
2161
- ? "pie"
2162
- : chartData.type === "line"
2163
- ? "line"
2164
- : "bar";
2165
- const series = chartData.series || [];
2166
- if (series.length === 0) {
2167
- return;
2168
- }
2169
- const chartDataForPptx = series.map((s) => ({
2170
- name: s.name || "Data",
2171
- labels: s.labels || [],
2172
- values: s.values || [],
2173
- }));
2174
- try {
2175
- slide.addChart(chartType, chartDataForPptx, {
2176
- x: pos.x + 0.1,
2177
- y: contentY,
2178
- w: pos.w - 0.2,
2179
- h: contentH,
2180
- showLegend: false,
2181
- showTitle: false,
2182
- chartColors: [
2183
- theme.colors.primary.replace("#", ""),
2184
- theme.colors.secondary.replace("#", ""),
2185
- theme.colors.accent.replace("#", ""),
2186
- theme.colors.muted.replace("#", ""),
2187
- ],
2188
- });
2189
- }
2190
- catch {
2191
- // Fallback: show placeholder
2192
- slide.addText("Chart", {
2193
- x: pos.x,
2194
- y: contentY,
2195
- w: pos.w,
2196
- h: contentH,
2197
- fontSize: 14,
2198
- color: theme.colors.muted.replace("#", ""),
2199
- align: "center",
2200
- valign: "middle",
2201
- fit: DEFAULT_TEXT_FIT,
2202
- });
2203
- }
2204
- }
2205
- /**
2206
- * Render a composite/dashboard slide with multiple content zones
2207
- *
2208
- * @example
2209
- * // Left: bullets, Right: chart
2210
- * renderDashboardSlide(slide, "Overview", {
2211
- * layout: "left-right",
2212
- * zones: [
2213
- * { type: "bullets", title: "Key Points", data: bullets },
2214
- * { type: "chart", title: "Trend", data: chartData }
2215
- * ]
2216
- * }, theme);
2217
- */
2218
- export function renderDashboardSlide(slide, title, content, theme) {
2219
- addTitle(slide, title, theme, false);
2220
- const layoutPositions = COMPOSITE_LAYOUTS[content.layout] || COMPOSITE_LAYOUTS["left-right"];
2221
- const zones = content.zones || [];
2222
- zones.forEach((zone, idx) => {
2223
- if (idx >= layoutPositions.length) {
2224
- return;
2225
- }
2226
- const pos = layoutPositions[idx];
2227
- const { contentY, contentH } = renderContentBox(slide, pos, zone.title, theme, zone.isPrimary);
2228
- switch (zone.type) {
2229
- case "bullets":
2230
- if (Array.isArray(zone.data)) {
2231
- renderBulletsInZone(slide, zone.data, pos, theme, contentY, contentH);
2232
- }
2233
- break;
2234
- case "stats":
2235
- if (zone.data && typeof zone.data === "object") {
2236
- renderStatInZone(slide, zone.data, pos, theme, contentY, contentH);
2237
- }
2238
- break;
2239
- case "icon-box":
2240
- if (zone.data && typeof zone.data === "object") {
2241
- const iconData = zone.data;
2242
- renderIconBoxInZone({
2243
- slide,
2244
- icon: iconData.icon,
2245
- label: iconData.label,
2246
- description: iconData.description,
2247
- pos,
2248
- theme,
2249
- contentY,
2250
- contentH,
2251
- });
2252
- }
2253
- break;
2254
- case "chart":
2255
- if (zone.data && typeof zone.data === "object") {
2256
- renderMiniChartInZone(slide, zone.data, pos, theme, contentY, contentH);
2257
- }
2258
- break;
2259
- case "text-box":
2260
- if (typeof zone.data === "string") {
2261
- slide.addText(zone.data, {
2262
- x: pos.x + 0.15,
2263
- y: contentY,
2264
- w: pos.w - 0.3,
2265
- h: contentH,
2266
- fontSize: 12,
2267
- fontFace: theme.fonts.body,
2268
- color: theme.colors.text.replace("#", ""),
2269
- valign: "top",
2270
- });
2271
- }
2272
- break;
2273
- }
2274
- });
2275
- }
2276
- /**
2277
- * Render a mixed content slide with left bullets and right chart
2278
- * Common pattern: explanation on left, visualization on right
2279
- */
2280
- export function renderMixedContentSlide(slide, title, content, theme) {
2281
- addTitle(slide, title, theme, false);
2282
- // Left side: bullets or text
2283
- if (content.bullets && content.bullets.length > 0) {
2284
- const leftPos = COMPOSITE_LAYOUTS["left-right"][0];
2285
- const { contentY, contentH } = renderContentBox(slide, leftPos, content.leftColumn?.title, theme, true);
2286
- renderBulletsInZone(slide, content.bullets, leftPos, theme, contentY, contentH);
2287
- }
2288
- // Right side: chart if available
2289
- if (content.chartData) {
2290
- const rightPos = COMPOSITE_LAYOUTS["left-right"][1];
2291
- const { contentY, contentH } = renderContentBox(slide, rightPos, "Data", theme, false);
2292
- renderMiniChartInZone(slide, content.chartData, rightPos, theme, contentY, contentH);
2293
- }
2294
- // Or statistics
2295
- else if (content.statistics && content.statistics.length > 0) {
2296
- const rightPos = COMPOSITE_LAYOUTS["left-right"][1];
2297
- renderContentBox(slide, rightPos, "Metrics", theme, false);
2298
- // Show up to 3 stats stacked
2299
- const stats = content.statistics.slice(0, 3);
2300
- const statHeight = rightPos.h / stats.length;
2301
- stats.forEach((stat, idx) => {
2302
- renderStatInZone(slide, stat, { ...rightPos, y: rightPos.y + idx * statHeight, h: statHeight }, theme, rightPos.y + idx * statHeight + 0.1, statHeight - 0.2);
2303
- });
2304
- }
2305
- }
2306
- /**
2307
- * Render a stats grid slide with multiple stat boxes
2308
- */
2309
- export function renderStatsGridSlide(slide, title, content, theme) {
2310
- addTitle(slide, title, theme, false);
2311
- const stats = content.statistics || [];
2312
- if (stats.length === 0) {
2313
- return;
2314
- }
2315
- // Choose layout based on stat count
2316
- let layout;
2317
- if (stats.length <= 2) {
2318
- layout = COMPOSITE_LAYOUTS["left-right"];
2319
- }
2320
- else if (stats.length <= 3) {
2321
- layout = COMPOSITE_LAYOUTS["three-cols"];
2322
- }
2323
- else if (stats.length <= 4) {
2324
- layout = COMPOSITE_LAYOUTS["quadrants"];
2325
- }
2326
- else if (stats.length <= 5) {
2327
- layout = COMPOSITE_LAYOUTS["five-boxes"];
2328
- }
2329
- else {
2330
- layout = COMPOSITE_LAYOUTS["six-boxes"];
2331
- }
2332
- stats.slice(0, layout.length).forEach((stat, idx) => {
2333
- const pos = layout[idx];
2334
- const isPrimary = idx === 0;
2335
- const { contentY, contentH } = renderContentBox(slide, pos, undefined, theme, isPrimary);
2336
- renderStatInZone(slide, stat, pos, theme, contentY, contentH);
2337
- });
2338
- }
2339
- /**
2340
- * Render an icon grid slide with multiple icon boxes
2341
- */
2342
- export function renderIconGridSlide(slide, title, content, theme) {
2343
- addTitle(slide, title, theme, false);
2344
- const icons = content.icons || content.features || [];
2345
- if (icons.length === 0) {
2346
- return;
2347
- }
2348
- // Choose layout based on icon count
2349
- let layout;
2350
- if (icons.length <= 2) {
2351
- layout = COMPOSITE_LAYOUTS["left-right"];
2352
- }
2353
- else if (icons.length <= 3) {
2354
- layout = COMPOSITE_LAYOUTS["three-cols"];
2355
- }
2356
- else if (icons.length <= 4) {
2357
- layout = COMPOSITE_LAYOUTS["quadrants"];
2358
- }
2359
- else if (icons.length <= 5) {
2360
- layout = COMPOSITE_LAYOUTS["five-boxes"];
2361
- }
2362
- else {
2363
- layout = COMPOSITE_LAYOUTS["six-boxes"];
2364
- }
2365
- icons.slice(0, layout.length).forEach((item, idx) => {
2366
- const pos = layout[idx];
2367
- const isPrimary = idx === 0;
2368
- const { contentY, contentH } = renderContentBox(slide, pos, undefined, theme, isPrimary);
2369
- const icon = item.icon || "1F4A1";
2370
- const label = item.title || item.label || "";
2371
- const description = item.description;
2372
- renderIconBoxInZone({
2373
- slide,
2374
- icon,
2375
- label,
2376
- description,
2377
- pos,
2378
- theme,
2379
- contentY,
2380
- contentH,
2381
- });
2382
- });
2383
- }