@juspay/neurolink 9.32.0 → 9.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. package/CHANGELOG.md +12 -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/neurolink.d.ts +10 -0
  11. package/dist/lib/neurolink.js +41 -7
  12. package/dist/lib/server/routes/claudeProxyRoutes.js +45 -9
  13. package/dist/lib/types/generateTypes.d.ts +16 -0
  14. package/dist/lib/types/streamTypes.d.ts +15 -0
  15. package/dist/mcp/elicitationProtocol.js +1 -1
  16. package/dist/mcp/servers/agent/directToolsServer.js +0 -1
  17. package/dist/neurolink.d.ts +10 -0
  18. package/dist/neurolink.js +41 -7
  19. package/dist/providers/azureOpenai.js +1 -1
  20. package/dist/providers/huggingFace.js +0 -1
  21. package/dist/providers/openaiCompatible.js +0 -1
  22. package/dist/sdk/toolRegistration.js +0 -1
  23. package/dist/server/openapi/generator.js +1 -1
  24. package/dist/server/routes/claudeProxyRoutes.js +45 -9
  25. package/dist/types/configTypes.js +0 -5
  26. package/dist/types/generateTypes.d.ts +16 -0
  27. package/dist/types/modelTypes.js +0 -1
  28. package/dist/types/streamTypes.d.ts +15 -0
  29. package/dist/types/tools.js +0 -1
  30. package/dist/types/typeAliases.js +0 -1
  31. package/dist/types/utilities.js +1 -1
  32. package/dist/types/workflowTypes.js +0 -1
  33. package/dist/utils/providerRetry.js +0 -1
  34. package/dist/utils/providerUtils.js +0 -1
  35. package/package.json +2 -2
  36. package/dist/client/adapters/providerImageAdapter.js +0 -588
  37. package/dist/client/adapters/tts/googleTTSHandler.js +0 -344
  38. package/dist/client/adapters/video/directorPipeline.js +0 -516
  39. package/dist/client/adapters/video/ffmpegAdapter.js +0 -206
  40. package/dist/client/adapters/video/frameExtractor.js +0 -143
  41. package/dist/client/adapters/video/vertexVideoHandler.js +0 -763
  42. package/dist/client/adapters/video/videoAnalyzer.js +0 -238
  43. package/dist/client/adapters/video/videoMerger.js +0 -171
  44. package/dist/client/agent/directTools.js +0 -840
  45. package/dist/client/auth/AuthProviderFactory.js +0 -111
  46. package/dist/client/auth/AuthProviderRegistry.js +0 -190
  47. package/dist/client/auth/RequestContext.js +0 -78
  48. package/dist/client/auth/accountPool.js +0 -178
  49. package/dist/client/auth/anthropicOAuth.js +0 -974
  50. package/dist/client/auth/authContext.js +0 -314
  51. package/dist/client/auth/errors.js +0 -39
  52. package/dist/client/auth/index.js +0 -61
  53. package/dist/client/auth/middleware/AuthMiddleware.js +0 -519
  54. package/dist/client/auth/middleware/rateLimitByUser.js +0 -554
  55. package/dist/client/auth/providers/BaseAuthProvider.js +0 -723
  56. package/dist/client/auth/providers/CognitoProvider.js +0 -304
  57. package/dist/client/auth/providers/KeycloakProvider.js +0 -393
  58. package/dist/client/auth/providers/auth0.js +0 -274
  59. package/dist/client/auth/providers/betterAuth.js +0 -182
  60. package/dist/client/auth/providers/clerk.js +0 -317
  61. package/dist/client/auth/providers/custom.js +0 -112
  62. package/dist/client/auth/providers/firebase.js +0 -226
  63. package/dist/client/auth/providers/jwt.js +0 -212
  64. package/dist/client/auth/providers/oauth2.js +0 -303
  65. package/dist/client/auth/providers/supabase.js +0 -259
  66. package/dist/client/auth/providers/workos.js +0 -284
  67. package/dist/client/auth/serverBridge.js +0 -25
  68. package/dist/client/auth/sessionManager.js +0 -437
  69. package/dist/client/auth/tokenStore.js +0 -799
  70. package/dist/client/client/aiSdkAdapter.js +0 -487
  71. package/dist/client/client/auth.js +0 -473
  72. package/dist/client/client/errors.js +0 -552
  73. package/dist/client/client/httpClient.js +0 -837
  74. package/dist/client/client/index.js +0 -172
  75. package/dist/client/client/interceptors.js +0 -601
  76. package/dist/client/client/sseClient.js +0 -545
  77. package/dist/client/client/streamingClient.js +0 -917
  78. package/dist/client/client/wsClient.js +0 -369
  79. package/dist/client/config/configManager.js +0 -303
  80. package/dist/client/config/conversationMemory.js +0 -86
  81. package/dist/client/config/taskClassificationConfig.js +0 -148
  82. package/dist/client/constants/contextWindows.js +0 -295
  83. package/dist/client/constants/enums.js +0 -853
  84. package/dist/client/constants/index.js +0 -207
  85. package/dist/client/constants/performance.js +0 -389
  86. package/dist/client/constants/retry.js +0 -266
  87. package/dist/client/constants/timeouts.js +0 -182
  88. package/dist/client/constants/tokens.js +0 -380
  89. package/dist/client/constants/videoErrors.js +0 -46
  90. package/dist/client/context/budgetChecker.js +0 -98
  91. package/dist/client/context/contextCompactor.js +0 -205
  92. package/dist/client/context/emergencyTruncation.js +0 -88
  93. package/dist/client/context/errorDetection.js +0 -171
  94. package/dist/client/context/errors.js +0 -21
  95. package/dist/client/context/fileTokenBudget.js +0 -127
  96. package/dist/client/context/prompts/summarizationPrompt.js +0 -117
  97. package/dist/client/context/stages/fileReadDeduplicator.js +0 -66
  98. package/dist/client/context/stages/slidingWindowTruncator.js +0 -190
  99. package/dist/client/context/stages/structuredSummarizer.js +0 -99
  100. package/dist/client/context/stages/toolOutputPruner.js +0 -52
  101. package/dist/client/context/summarizationEngine.js +0 -136
  102. package/dist/client/context/toolOutputLimits.js +0 -78
  103. package/dist/client/context/toolPairRepair.js +0 -66
  104. package/dist/client/core/analytics.js +0 -88
  105. package/dist/client/core/baseProvider.js +0 -1385
  106. package/dist/client/core/constants.js +0 -140
  107. package/dist/client/core/conversationMemoryFactory.js +0 -141
  108. package/dist/client/core/conversationMemoryInitializer.js +0 -128
  109. package/dist/client/core/conversationMemoryManager.js +0 -344
  110. package/dist/client/core/dynamicModels.js +0 -358
  111. package/dist/client/core/evaluation.js +0 -309
  112. package/dist/client/core/evaluationProviders.js +0 -248
  113. package/dist/client/core/factory.js +0 -412
  114. package/dist/client/core/infrastructure/baseError.js +0 -22
  115. package/dist/client/core/infrastructure/baseFactory.js +0 -54
  116. package/dist/client/core/infrastructure/baseRegistry.js +0 -53
  117. package/dist/client/core/infrastructure/index.js +0 -5
  118. package/dist/client/core/infrastructure/retry.js +0 -20
  119. package/dist/client/core/infrastructure/typedEventEmitter.js +0 -23
  120. package/dist/client/core/modelConfiguration.js +0 -851
  121. package/dist/client/core/modules/GenerationHandler.js +0 -588
  122. package/dist/client/core/modules/MessageBuilder.js +0 -273
  123. package/dist/client/core/modules/StreamHandler.js +0 -185
  124. package/dist/client/core/modules/TelemetryHandler.js +0 -203
  125. package/dist/client/core/modules/ToolsManager.js +0 -499
  126. package/dist/client/core/modules/Utilities.js +0 -331
  127. package/dist/client/core/redisConversationMemoryManager.js +0 -1435
  128. package/dist/client/core/streamAnalytics.js +0 -131
  129. package/dist/client/evaluation/contextBuilder.js +0 -134
  130. package/dist/client/evaluation/index.js +0 -61
  131. package/dist/client/evaluation/prompts.js +0 -73
  132. package/dist/client/evaluation/ragasEvaluator.js +0 -110
  133. package/dist/client/evaluation/retryManager.js +0 -78
  134. package/dist/client/evaluation/scoring.js +0 -61
  135. package/dist/client/factories/providerFactory.js +0 -166
  136. package/dist/client/factories/providerRegistry.js +0 -166
  137. package/dist/client/features/ppt/constants.js +0 -896
  138. package/dist/client/features/ppt/contentPlanner.js +0 -529
  139. package/dist/client/features/ppt/presentationOrchestrator.js +0 -236
  140. package/dist/client/features/ppt/slideGenerator.js +0 -532
  141. package/dist/client/features/ppt/slideRenderers.js +0 -2383
  142. package/dist/client/features/ppt/slideTypeInference.js +0 -405
  143. package/dist/client/features/ppt/types.js +0 -13
  144. package/dist/client/features/ppt/utils.js +0 -443
  145. package/dist/client/files/fileReferenceRegistry.js +0 -1543
  146. package/dist/client/files/fileTools.js +0 -450
  147. package/dist/client/files/streamingReader.js +0 -321
  148. package/dist/client/files/types.js +0 -23
  149. package/dist/client/hitl/hitlErrors.js +0 -54
  150. package/dist/client/hitl/hitlManager.js +0 -460
  151. package/dist/client/mcp/agentExposure.js +0 -356
  152. package/dist/client/mcp/auth/index.js +0 -11
  153. package/dist/client/mcp/auth/oauthClientProvider.js +0 -325
  154. package/dist/client/mcp/auth/tokenStorage.js +0 -134
  155. package/dist/client/mcp/batching/index.js +0 -10
  156. package/dist/client/mcp/batching/requestBatcher.js +0 -441
  157. package/dist/client/mcp/caching/index.js +0 -10
  158. package/dist/client/mcp/caching/toolCache.js +0 -433
  159. package/dist/client/mcp/elicitation/elicitationManager.js +0 -376
  160. package/dist/client/mcp/elicitation/index.js +0 -11
  161. package/dist/client/mcp/elicitation/types.js +0 -10
  162. package/dist/client/mcp/elicitationProtocol.js +0 -375
  163. package/dist/client/mcp/enhancedToolDiscovery.js +0 -481
  164. package/dist/client/mcp/externalServerManager.js +0 -1478
  165. package/dist/client/mcp/factory.js +0 -161
  166. package/dist/client/mcp/flexibleToolValidator.js +0 -161
  167. package/dist/client/mcp/httpRateLimiter.js +0 -391
  168. package/dist/client/mcp/httpRetryHandler.js +0 -178
  169. package/dist/client/mcp/index.js +0 -74
  170. package/dist/client/mcp/mcpCircuitBreaker.js +0 -427
  171. package/dist/client/mcp/mcpClientFactory.js +0 -708
  172. package/dist/client/mcp/mcpRegistryClient.js +0 -488
  173. package/dist/client/mcp/mcpServerBase.js +0 -373
  174. package/dist/client/mcp/multiServerManager.js +0 -579
  175. package/dist/client/mcp/registry.js +0 -158
  176. package/dist/client/mcp/routing/index.js +0 -10
  177. package/dist/client/mcp/routing/toolRouter.js +0 -416
  178. package/dist/client/mcp/serverCapabilities.js +0 -502
  179. package/dist/client/mcp/servers/agent/directToolsServer.js +0 -150
  180. package/dist/client/mcp/toolAnnotations.js +0 -239
  181. package/dist/client/mcp/toolConverter.js +0 -258
  182. package/dist/client/mcp/toolDiscoveryService.js +0 -798
  183. package/dist/client/mcp/toolIntegration.js +0 -334
  184. package/dist/client/mcp/toolRegistry.js +0 -729
  185. package/dist/client/memory/hippocampusInitializer.js +0 -19
  186. package/dist/client/memory/memoryRetrievalTools.js +0 -166
  187. package/dist/client/middleware/builtin/analytics.js +0 -132
  188. package/dist/client/middleware/builtin/autoEvaluation.js +0 -203
  189. package/dist/client/middleware/builtin/guardrails.js +0 -109
  190. package/dist/client/middleware/builtin/lifecycle.js +0 -168
  191. package/dist/client/middleware/factory.js +0 -327
  192. package/dist/client/middleware/registry.js +0 -295
  193. package/dist/client/middleware/utils/guardrailsUtils.js +0 -396
  194. package/dist/client/models/anthropicModels.js +0 -527
  195. package/dist/client/neurolink.js +0 -8233
  196. package/dist/client/observability/exporterRegistry.js +0 -413
  197. package/dist/client/observability/exporters/arizeExporter.js +0 -138
  198. package/dist/client/observability/exporters/baseExporter.js +0 -190
  199. package/dist/client/observability/exporters/braintrustExporter.js +0 -154
  200. package/dist/client/observability/exporters/datadogExporter.js +0 -196
  201. package/dist/client/observability/exporters/laminarExporter.js +0 -302
  202. package/dist/client/observability/exporters/langfuseExporter.js +0 -209
  203. package/dist/client/observability/exporters/langsmithExporter.js +0 -143
  204. package/dist/client/observability/exporters/otelExporter.js +0 -164
  205. package/dist/client/observability/exporters/posthogExporter.js +0 -287
  206. package/dist/client/observability/exporters/sentryExporter.js +0 -165
  207. package/dist/client/observability/index.js +0 -31
  208. package/dist/client/observability/metricsAggregator.js +0 -556
  209. package/dist/client/observability/otelBridge.js +0 -131
  210. package/dist/client/observability/retryPolicy.js +0 -383
  211. package/dist/client/observability/sampling/samplers.js +0 -216
  212. package/dist/client/observability/spanProcessor.js +0 -303
  213. package/dist/client/observability/tokenTracker.js +0 -413
  214. package/dist/client/observability/types/exporterTypes.js +0 -5
  215. package/dist/client/observability/types/index.js +0 -4
  216. package/dist/client/observability/types/spanTypes.js +0 -92
  217. package/dist/client/observability/utils/safeMetadata.js +0 -25
  218. package/dist/client/observability/utils/spanSerializer.js +0 -292
  219. package/dist/client/processors/archive/ArchiveProcessor.js +0 -1308
  220. package/dist/client/processors/base/BaseFileProcessor.js +0 -614
  221. package/dist/client/processors/base/types.js +0 -82
  222. package/dist/client/processors/config/fileTypes.js +0 -520
  223. package/dist/client/processors/config/index.js +0 -92
  224. package/dist/client/processors/config/languageMap.js +0 -410
  225. package/dist/client/processors/config/mimeTypes.js +0 -363
  226. package/dist/client/processors/config/sizeLimits.js +0 -258
  227. package/dist/client/processors/document/ExcelProcessor.js +0 -590
  228. package/dist/client/processors/document/OpenDocumentProcessor.js +0 -212
  229. package/dist/client/processors/document/PptxProcessor.js +0 -157
  230. package/dist/client/processors/document/RtfProcessor.js +0 -361
  231. package/dist/client/processors/document/WordProcessor.js +0 -353
  232. package/dist/client/processors/errors/FileErrorCode.js +0 -255
  233. package/dist/client/processors/errors/errorHelpers.js +0 -386
  234. package/dist/client/processors/errors/errorSerializer.js +0 -507
  235. package/dist/client/processors/errors/index.js +0 -49
  236. package/dist/client/processors/markup/SvgProcessor.js +0 -240
  237. package/dist/client/processors/media/AudioProcessor.js +0 -707
  238. package/dist/client/processors/media/VideoProcessor.js +0 -1045
  239. package/dist/client/providers/amazonBedrock.js +0 -1512
  240. package/dist/client/providers/amazonSagemaker.js +0 -162
  241. package/dist/client/providers/anthropic.js +0 -831
  242. package/dist/client/providers/azureOpenai.js +0 -143
  243. package/dist/client/providers/googleAiStudio.js +0 -1200
  244. package/dist/client/providers/googleNativeGemini3.js +0 -543
  245. package/dist/client/providers/googleVertex.js +0 -2936
  246. package/dist/client/providers/huggingFace.js +0 -315
  247. package/dist/client/providers/litellm.js +0 -488
  248. package/dist/client/providers/mistral.js +0 -157
  249. package/dist/client/providers/ollama.js +0 -1579
  250. package/dist/client/providers/openAI.js +0 -627
  251. package/dist/client/providers/openRouter.js +0 -543
  252. package/dist/client/providers/openaiCompatible.js +0 -290
  253. package/dist/client/providers/providerTypeUtils.js +0 -46
  254. package/dist/client/providers/sagemaker/adaptive-semaphore.js +0 -215
  255. package/dist/client/providers/sagemaker/client.js +0 -472
  256. package/dist/client/providers/sagemaker/config.js +0 -317
  257. package/dist/client/providers/sagemaker/detection.js +0 -606
  258. package/dist/client/providers/sagemaker/error-constants.js +0 -227
  259. package/dist/client/providers/sagemaker/errors.js +0 -299
  260. package/dist/client/providers/sagemaker/language-model.js +0 -775
  261. package/dist/client/providers/sagemaker/parsers.js +0 -634
  262. package/dist/client/providers/sagemaker/streaming.js +0 -331
  263. package/dist/client/providers/sagemaker/structured-parser.js +0 -625
  264. package/dist/client/proxy/accountQuota.js +0 -162
  265. package/dist/client/proxy/claudeFormat.js +0 -595
  266. package/dist/client/proxy/modelRouter.js +0 -29
  267. package/dist/client/proxy/oauthFetch.js +0 -367
  268. package/dist/client/proxy/proxyFetch.js +0 -586
  269. package/dist/client/proxy/requestLogger.js +0 -207
  270. package/dist/client/proxy/tokenRefresh.js +0 -124
  271. package/dist/client/proxy/usageStats.js +0 -74
  272. package/dist/client/proxy/utils/noProxyUtils.js +0 -149
  273. package/dist/client/rag/ChunkerFactory.js +0 -320
  274. package/dist/client/rag/ChunkerRegistry.js +0 -421
  275. package/dist/client/rag/chunkers/BaseChunker.js +0 -143
  276. package/dist/client/rag/chunkers/CharacterChunker.js +0 -28
  277. package/dist/client/rag/chunkers/HTMLChunker.js +0 -38
  278. package/dist/client/rag/chunkers/JSONChunker.js +0 -68
  279. package/dist/client/rag/chunkers/LaTeXChunker.js +0 -63
  280. package/dist/client/rag/chunkers/MarkdownChunker.js +0 -306
  281. package/dist/client/rag/chunkers/RecursiveChunker.js +0 -139
  282. package/dist/client/rag/chunkers/SemanticMarkdownChunker.js +0 -138
  283. package/dist/client/rag/chunkers/SentenceChunker.js +0 -66
  284. package/dist/client/rag/chunkers/TokenChunker.js +0 -61
  285. package/dist/client/rag/chunkers/index.js +0 -15
  286. package/dist/client/rag/chunking/characterChunker.js +0 -142
  287. package/dist/client/rag/chunking/chunkerRegistry.js +0 -194
  288. package/dist/client/rag/chunking/htmlChunker.js +0 -247
  289. package/dist/client/rag/chunking/index.js +0 -17
  290. package/dist/client/rag/chunking/jsonChunker.js +0 -281
  291. package/dist/client/rag/chunking/latexChunker.js +0 -251
  292. package/dist/client/rag/chunking/markdownChunker.js +0 -373
  293. package/dist/client/rag/chunking/recursiveChunker.js +0 -148
  294. package/dist/client/rag/chunking/semanticChunker.js +0 -306
  295. package/dist/client/rag/chunking/sentenceChunker.js +0 -230
  296. package/dist/client/rag/chunking/tokenChunker.js +0 -183
  297. package/dist/client/rag/document/MDocument.js +0 -392
  298. package/dist/client/rag/document/index.js +0 -5
  299. package/dist/client/rag/document/loaders.js +0 -500
  300. package/dist/client/rag/errors/RAGError.js +0 -274
  301. package/dist/client/rag/errors/index.js +0 -6
  302. package/dist/client/rag/graphRag/graphRAG.js +0 -401
  303. package/dist/client/rag/graphRag/index.js +0 -4
  304. package/dist/client/rag/index.js +0 -141
  305. package/dist/client/rag/metadata/MetadataExtractorFactory.js +0 -418
  306. package/dist/client/rag/metadata/MetadataExtractorRegistry.js +0 -362
  307. package/dist/client/rag/metadata/index.js +0 -9
  308. package/dist/client/rag/metadata/metadataExtractor.js +0 -280
  309. package/dist/client/rag/pipeline/RAGPipeline.js +0 -436
  310. package/dist/client/rag/pipeline/contextAssembly.js +0 -341
  311. package/dist/client/rag/pipeline/index.js +0 -5
  312. package/dist/client/rag/ragIntegration.js +0 -321
  313. package/dist/client/rag/reranker/RerankerFactory.js +0 -430
  314. package/dist/client/rag/reranker/RerankerRegistry.js +0 -402
  315. package/dist/client/rag/reranker/index.js +0 -9
  316. package/dist/client/rag/reranker/reranker.js +0 -277
  317. package/dist/client/rag/resilience/CircuitBreaker.js +0 -431
  318. package/dist/client/rag/resilience/RetryHandler.js +0 -304
  319. package/dist/client/rag/resilience/index.js +0 -7
  320. package/dist/client/rag/retrieval/hybridSearch.js +0 -335
  321. package/dist/client/rag/retrieval/index.js +0 -5
  322. package/dist/client/rag/retrieval/vectorQueryTool.js +0 -307
  323. package/dist/client/rag/types.js +0 -8
  324. package/dist/client/sdk/toolRegistration.js +0 -377
  325. package/dist/client/server/abstract/baseServerAdapter.js +0 -575
  326. package/dist/client/server/adapters/expressAdapter.js +0 -486
  327. package/dist/client/server/adapters/fastifyAdapter.js +0 -472
  328. package/dist/client/server/adapters/honoAdapter.js +0 -632
  329. package/dist/client/server/adapters/koaAdapter.js +0 -510
  330. package/dist/client/server/errors.js +0 -486
  331. package/dist/client/server/factory/serverAdapterFactory.js +0 -160
  332. package/dist/client/server/index.js +0 -108
  333. package/dist/client/server/middleware/abortSignal.js +0 -111
  334. package/dist/client/server/middleware/auth.js +0 -388
  335. package/dist/client/server/middleware/cache.js +0 -359
  336. package/dist/client/server/middleware/common.js +0 -281
  337. package/dist/client/server/middleware/deprecation.js +0 -190
  338. package/dist/client/server/middleware/mcpBodyAttachment.js +0 -63
  339. package/dist/client/server/middleware/rateLimit.js +0 -227
  340. package/dist/client/server/middleware/validation.js +0 -388
  341. package/dist/client/server/openapi/generator.js +0 -398
  342. package/dist/client/server/openapi/index.js +0 -36
  343. package/dist/client/server/openapi/schemas.js +0 -695
  344. package/dist/client/server/openapi/templates.js +0 -374
  345. package/dist/client/server/routes/agentRoutes.js +0 -189
  346. package/dist/client/server/routes/claudeProxyRoutes.js +0 -1600
  347. package/dist/client/server/routes/healthRoutes.js +0 -187
  348. package/dist/client/server/routes/index.js +0 -57
  349. package/dist/client/server/routes/mcpRoutes.js +0 -342
  350. package/dist/client/server/routes/memoryRoutes.js +0 -350
  351. package/dist/client/server/routes/openApiRoutes.js +0 -126
  352. package/dist/client/server/routes/toolRoutes.js +0 -199
  353. package/dist/client/server/streaming/dataStream.js +0 -486
  354. package/dist/client/server/streaming/index.js +0 -11
  355. package/dist/client/server/types.js +0 -67
  356. package/dist/client/server/utils/redaction.js +0 -334
  357. package/dist/client/server/utils/validation.js +0 -243
  358. package/dist/client/server/websocket/WebSocketHandler.js +0 -383
  359. package/dist/client/server/websocket/index.js +0 -4
  360. package/dist/client/services/server/ai/observability/instrumentation.js +0 -808
  361. package/dist/client/telemetry/attributes.js +0 -100
  362. package/dist/client/telemetry/index.js +0 -26
  363. package/dist/client/telemetry/telemetryService.js +0 -308
  364. package/dist/client/telemetry/tracers.js +0 -17
  365. package/dist/client/telemetry/withSpan.js +0 -34
  366. package/dist/client/types/actionTypes.js +0 -6
  367. package/dist/client/types/analytics.js +0 -5
  368. package/dist/client/types/authTypes.js +0 -9
  369. package/dist/client/types/circuitBreakerErrors.js +0 -34
  370. package/dist/client/types/cli.js +0 -21
  371. package/dist/client/types/clientTypes.js +0 -10
  372. package/dist/client/types/common.js +0 -51
  373. package/dist/client/types/configTypes.js +0 -49
  374. package/dist/client/types/content.js +0 -19
  375. package/dist/client/types/contextTypes.js +0 -400
  376. package/dist/client/types/conversation.js +0 -47
  377. package/dist/client/types/conversationMemoryInterface.js +0 -6
  378. package/dist/client/types/domainTypes.js +0 -5
  379. package/dist/client/types/errors.js +0 -167
  380. package/dist/client/types/evaluation.js +0 -5
  381. package/dist/client/types/evaluationProviders.js +0 -5
  382. package/dist/client/types/evaluationTypes.js +0 -1
  383. package/dist/client/types/externalMcp.js +0 -6
  384. package/dist/client/types/fileReferenceTypes.js +0 -8
  385. package/dist/client/types/fileTypes.js +0 -4
  386. package/dist/client/types/generateTypes.js +0 -1
  387. package/dist/client/types/guardrails.js +0 -1
  388. package/dist/client/types/hitlTypes.js +0 -8
  389. package/dist/client/types/index.js +0 -57
  390. package/dist/client/types/mcpTypes.js +0 -5
  391. package/dist/client/types/middlewareTypes.js +0 -1
  392. package/dist/client/types/modelTypes.js +0 -30
  393. package/dist/client/types/multimodal.js +0 -135
  394. package/dist/client/types/observability.js +0 -6
  395. package/dist/client/types/pptTypes.js +0 -82
  396. package/dist/client/types/providers.js +0 -111
  397. package/dist/client/types/proxyTypes.js +0 -16
  398. package/dist/client/types/ragTypes.js +0 -7
  399. package/dist/client/types/sdkTypes.js +0 -8
  400. package/dist/client/types/serviceTypes.js +0 -5
  401. package/dist/client/types/streamTypes.js +0 -1
  402. package/dist/client/types/subscriptionTypes.js +0 -9
  403. package/dist/client/types/taskClassificationTypes.js +0 -5
  404. package/dist/client/types/tools.js +0 -24
  405. package/dist/client/types/ttsTypes.js +0 -57
  406. package/dist/client/types/typeAliases.js +0 -48
  407. package/dist/client/types/utilities.js +0 -4
  408. package/dist/client/types/workflowTypes.js +0 -30
  409. package/dist/client/utils/async/withTimeout.js +0 -98
  410. package/dist/client/utils/asyncMutex.js +0 -60
  411. package/dist/client/utils/conversationMemory.js +0 -431
  412. package/dist/client/utils/csvProcessor.js +0 -846
  413. package/dist/client/utils/errorHandling.js +0 -936
  414. package/dist/client/utils/evaluationUtils.js +0 -131
  415. package/dist/client/utils/factoryProcessing.js +0 -589
  416. package/dist/client/utils/fileDetector.js +0 -2161
  417. package/dist/client/utils/imageCache.js +0 -376
  418. package/dist/client/utils/imageProcessor.js +0 -704
  419. package/dist/client/utils/logger.js +0 -491
  420. package/dist/client/utils/mcpDefaults.js +0 -134
  421. package/dist/client/utils/messageBuilder.js +0 -1653
  422. package/dist/client/utils/modelAliasResolver.js +0 -54
  423. package/dist/client/utils/modelDetection.js +0 -80
  424. package/dist/client/utils/modelRouter.js +0 -292
  425. package/dist/client/utils/multimodalOptionsBuilder.js +0 -65
  426. package/dist/client/utils/observabilityHelpers.js +0 -47
  427. package/dist/client/utils/parameterValidation.js +0 -966
  428. package/dist/client/utils/pdfProcessor.js +0 -410
  429. package/dist/client/utils/performance.js +0 -222
  430. package/dist/client/utils/pricing.js +0 -340
  431. package/dist/client/utils/promptRedaction.js +0 -62
  432. package/dist/client/utils/providerConfig.js +0 -1009
  433. package/dist/client/utils/providerHealth.js +0 -1237
  434. package/dist/client/utils/providerRetry.js +0 -112
  435. package/dist/client/utils/providerUtils.js +0 -434
  436. package/dist/client/utils/rateLimiter.js +0 -200
  437. package/dist/client/utils/redis.js +0 -368
  438. package/dist/client/utils/retryHandler.js +0 -269
  439. package/dist/client/utils/retryability.js +0 -22
  440. package/dist/client/utils/sanitizers/svg.js +0 -481
  441. package/dist/client/utils/schemaConversion.js +0 -255
  442. package/dist/client/utils/taskClassificationUtils.js +0 -149
  443. package/dist/client/utils/taskClassifier.js +0 -94
  444. package/dist/client/utils/thinkingConfig.js +0 -104
  445. package/dist/client/utils/timeout.js +0 -359
  446. package/dist/client/utils/tokenEstimation.js +0 -142
  447. package/dist/client/utils/tokenLimits.js +0 -125
  448. package/dist/client/utils/tokenUtils.js +0 -239
  449. package/dist/client/utils/toolUtils.js +0 -75
  450. package/dist/client/utils/transformationUtils.js +0 -554
  451. package/dist/client/utils/ttsProcessor.js +0 -286
  452. package/dist/client/utils/typeUtils.js +0 -97
  453. package/dist/client/utils/videoAnalysisProcessor.js +0 -67
  454. package/dist/client/workflow/config.js +0 -398
  455. package/dist/client/workflow/core/ensembleExecutor.js +0 -407
  456. package/dist/client/workflow/core/judgeScorer.js +0 -544
  457. package/dist/client/workflow/core/responseConditioner.js +0 -225
  458. package/dist/client/workflow/core/types/conditionerTypes.js +0 -7
  459. package/dist/client/workflow/core/types/ensembleTypes.js +0 -7
  460. package/dist/client/workflow/core/types/index.js +0 -7
  461. package/dist/client/workflow/core/types/judgeTypes.js +0 -7
  462. package/dist/client/workflow/core/types/layerTypes.js +0 -7
  463. package/dist/client/workflow/core/types/registryTypes.js +0 -7
  464. package/dist/client/workflow/core/workflowRegistry.js +0 -304
  465. package/dist/client/workflow/core/workflowRunner.js +0 -586
  466. package/dist/client/workflow/index.js +0 -50
  467. package/dist/client/workflow/types.js +0 -9
  468. package/dist/client/workflow/utils/types/index.js +0 -7
  469. package/dist/client/workflow/utils/workflowMetrics.js +0 -311
  470. package/dist/client/workflow/utils/workflowValidation.js +0 -420
  471. package/dist/client/workflow/workflows/adaptiveWorkflow.js +0 -366
  472. package/dist/client/workflow/workflows/consensusWorkflow.js +0 -192
  473. package/dist/client/workflow/workflows/fallbackWorkflow.js +0 -225
  474. package/dist/client/workflow/workflows/multiJudgeWorkflow.js +0 -351
  475. /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
- }