ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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 (535) hide show
  1. agentui/.prettierrc +15 -0
  2. agentui/QUICKSTART.md +272 -0
  3. agentui/README.md +59 -0
  4. agentui/env.example +16 -0
  5. agentui/jsconfig.json +14 -0
  6. agentui/package-lock.json +4242 -0
  7. agentui/package.json +34 -0
  8. agentui/scripts/postinstall/apply-patches.mjs +260 -0
  9. agentui/src/app.css +61 -0
  10. agentui/src/app.d.ts +13 -0
  11. agentui/src/app.html +12 -0
  12. agentui/src/components/LoadingSpinner.svelte +64 -0
  13. agentui/src/components/ThemeSwitcher.svelte +159 -0
  14. agentui/src/components/index.js +4 -0
  15. agentui/src/lib/api/bots.ts +60 -0
  16. agentui/src/lib/api/chat.ts +22 -0
  17. agentui/src/lib/api/http.ts +25 -0
  18. agentui/src/lib/components/BotCard.svelte +33 -0
  19. agentui/src/lib/components/ChatBubble.svelte +63 -0
  20. agentui/src/lib/components/Toast.svelte +21 -0
  21. agentui/src/lib/config.ts +20 -0
  22. agentui/src/lib/stores/auth.svelte.ts +73 -0
  23. agentui/src/lib/stores/theme.svelte.js +64 -0
  24. agentui/src/lib/stores/toast.svelte.ts +31 -0
  25. agentui/src/lib/utils/conversation.ts +39 -0
  26. agentui/src/routes/+layout.svelte +20 -0
  27. agentui/src/routes/+page.svelte +232 -0
  28. agentui/src/routes/login/+page.svelte +200 -0
  29. agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
  30. agentui/src/routes/talk/[agentId]/+page.ts +7 -0
  31. agentui/static/README.md +1 -0
  32. agentui/svelte.config.js +11 -0
  33. agentui/tailwind.config.ts +53 -0
  34. agentui/tsconfig.json +3 -0
  35. agentui/vite.config.ts +10 -0
  36. ai_parrot-0.17.2.dist-info/METADATA +472 -0
  37. ai_parrot-0.17.2.dist-info/RECORD +535 -0
  38. ai_parrot-0.17.2.dist-info/WHEEL +6 -0
  39. ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
  40. ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
  41. ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
  42. crew-builder/.prettierrc +15 -0
  43. crew-builder/QUICKSTART.md +259 -0
  44. crew-builder/README.md +113 -0
  45. crew-builder/env.example +17 -0
  46. crew-builder/jsconfig.json +14 -0
  47. crew-builder/package-lock.json +4182 -0
  48. crew-builder/package.json +37 -0
  49. crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
  50. crew-builder/src/app.css +62 -0
  51. crew-builder/src/app.d.ts +13 -0
  52. crew-builder/src/app.html +12 -0
  53. crew-builder/src/components/LoadingSpinner.svelte +64 -0
  54. crew-builder/src/components/ThemeSwitcher.svelte +149 -0
  55. crew-builder/src/components/index.js +9 -0
  56. crew-builder/src/lib/api/bots.ts +60 -0
  57. crew-builder/src/lib/api/chat.ts +80 -0
  58. crew-builder/src/lib/api/client.ts +56 -0
  59. crew-builder/src/lib/api/crew/crew.ts +136 -0
  60. crew-builder/src/lib/api/index.ts +5 -0
  61. crew-builder/src/lib/api/o365/auth.ts +65 -0
  62. crew-builder/src/lib/auth/auth.ts +54 -0
  63. crew-builder/src/lib/components/AgentNode.svelte +43 -0
  64. crew-builder/src/lib/components/BotCard.svelte +33 -0
  65. crew-builder/src/lib/components/ChatBubble.svelte +67 -0
  66. crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
  67. crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
  68. crew-builder/src/lib/components/JsonViewer.svelte +24 -0
  69. crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
  70. crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
  71. crew-builder/src/lib/components/Toast.svelte +67 -0
  72. crew-builder/src/lib/components/Toolbar.svelte +157 -0
  73. crew-builder/src/lib/components/index.ts +10 -0
  74. crew-builder/src/lib/config.ts +8 -0
  75. crew-builder/src/lib/stores/auth.svelte.ts +228 -0
  76. crew-builder/src/lib/stores/crewStore.ts +369 -0
  77. crew-builder/src/lib/stores/theme.svelte.js +145 -0
  78. crew-builder/src/lib/stores/toast.svelte.ts +69 -0
  79. crew-builder/src/lib/utils/conversation.ts +39 -0
  80. crew-builder/src/lib/utils/markdown.ts +122 -0
  81. crew-builder/src/lib/utils/talkHistory.ts +47 -0
  82. crew-builder/src/routes/+layout.svelte +20 -0
  83. crew-builder/src/routes/+page.svelte +539 -0
  84. crew-builder/src/routes/agents/+page.svelte +247 -0
  85. crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
  86. crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
  87. crew-builder/src/routes/builder/+page.svelte +204 -0
  88. crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
  89. crew-builder/src/routes/crew/ask/+page.ts +1 -0
  90. crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
  91. crew-builder/src/routes/login/+page.svelte +197 -0
  92. crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
  93. crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
  94. crew-builder/static/README.md +1 -0
  95. crew-builder/svelte.config.js +11 -0
  96. crew-builder/tailwind.config.ts +53 -0
  97. crew-builder/tsconfig.json +3 -0
  98. crew-builder/vite.config.ts +10 -0
  99. mcp_servers/calculator_server.py +309 -0
  100. parrot/__init__.py +27 -0
  101. parrot/__pycache__/__init__.cpython-310.pyc +0 -0
  102. parrot/__pycache__/version.cpython-310.pyc +0 -0
  103. parrot/_version.py +34 -0
  104. parrot/a2a/__init__.py +48 -0
  105. parrot/a2a/client.py +658 -0
  106. parrot/a2a/discovery.py +89 -0
  107. parrot/a2a/mixin.py +257 -0
  108. parrot/a2a/models.py +376 -0
  109. parrot/a2a/server.py +770 -0
  110. parrot/agents/__init__.py +29 -0
  111. parrot/bots/__init__.py +12 -0
  112. parrot/bots/a2a_agent.py +19 -0
  113. parrot/bots/abstract.py +3139 -0
  114. parrot/bots/agent.py +1129 -0
  115. parrot/bots/basic.py +9 -0
  116. parrot/bots/chatbot.py +669 -0
  117. parrot/bots/data.py +1618 -0
  118. parrot/bots/database/__init__.py +5 -0
  119. parrot/bots/database/abstract.py +3071 -0
  120. parrot/bots/database/cache.py +286 -0
  121. parrot/bots/database/models.py +468 -0
  122. parrot/bots/database/prompts.py +154 -0
  123. parrot/bots/database/retries.py +98 -0
  124. parrot/bots/database/router.py +269 -0
  125. parrot/bots/database/sql.py +41 -0
  126. parrot/bots/db/__init__.py +6 -0
  127. parrot/bots/db/abstract.py +556 -0
  128. parrot/bots/db/bigquery.py +602 -0
  129. parrot/bots/db/cache.py +85 -0
  130. parrot/bots/db/documentdb.py +668 -0
  131. parrot/bots/db/elastic.py +1014 -0
  132. parrot/bots/db/influx.py +898 -0
  133. parrot/bots/db/mock.py +96 -0
  134. parrot/bots/db/multi.py +783 -0
  135. parrot/bots/db/prompts.py +185 -0
  136. parrot/bots/db/sql.py +1255 -0
  137. parrot/bots/db/tools.py +212 -0
  138. parrot/bots/document.py +680 -0
  139. parrot/bots/hrbot.py +15 -0
  140. parrot/bots/kb.py +170 -0
  141. parrot/bots/mcp.py +36 -0
  142. parrot/bots/orchestration/README.md +463 -0
  143. parrot/bots/orchestration/__init__.py +1 -0
  144. parrot/bots/orchestration/agent.py +155 -0
  145. parrot/bots/orchestration/crew.py +3330 -0
  146. parrot/bots/orchestration/fsm.py +1179 -0
  147. parrot/bots/orchestration/hr.py +434 -0
  148. parrot/bots/orchestration/storage/__init__.py +4 -0
  149. parrot/bots/orchestration/storage/memory.py +100 -0
  150. parrot/bots/orchestration/storage/mixin.py +119 -0
  151. parrot/bots/orchestration/verify.py +202 -0
  152. parrot/bots/product.py +204 -0
  153. parrot/bots/prompts/__init__.py +96 -0
  154. parrot/bots/prompts/agents.py +155 -0
  155. parrot/bots/prompts/data.py +216 -0
  156. parrot/bots/prompts/output_generation.py +8 -0
  157. parrot/bots/scraper/__init__.py +3 -0
  158. parrot/bots/scraper/models.py +122 -0
  159. parrot/bots/scraper/scraper.py +1173 -0
  160. parrot/bots/scraper/templates.py +115 -0
  161. parrot/bots/stores/__init__.py +5 -0
  162. parrot/bots/stores/local.py +172 -0
  163. parrot/bots/webdev.py +81 -0
  164. parrot/cli.py +17 -0
  165. parrot/clients/__init__.py +16 -0
  166. parrot/clients/base.py +1491 -0
  167. parrot/clients/claude.py +1191 -0
  168. parrot/clients/factory.py +129 -0
  169. parrot/clients/google.py +4567 -0
  170. parrot/clients/gpt.py +1975 -0
  171. parrot/clients/grok.py +432 -0
  172. parrot/clients/groq.py +986 -0
  173. parrot/clients/hf.py +582 -0
  174. parrot/clients/models.py +18 -0
  175. parrot/conf.py +395 -0
  176. parrot/embeddings/__init__.py +9 -0
  177. parrot/embeddings/base.py +157 -0
  178. parrot/embeddings/google.py +98 -0
  179. parrot/embeddings/huggingface.py +74 -0
  180. parrot/embeddings/openai.py +84 -0
  181. parrot/embeddings/processor.py +88 -0
  182. parrot/exceptions.c +13868 -0
  183. parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
  184. parrot/exceptions.pxd +22 -0
  185. parrot/exceptions.pxi +15 -0
  186. parrot/exceptions.pyx +44 -0
  187. parrot/generators/__init__.py +29 -0
  188. parrot/generators/base.py +200 -0
  189. parrot/generators/html.py +293 -0
  190. parrot/generators/react.py +205 -0
  191. parrot/generators/streamlit.py +203 -0
  192. parrot/generators/template.py +105 -0
  193. parrot/handlers/__init__.py +4 -0
  194. parrot/handlers/agent.py +861 -0
  195. parrot/handlers/agents/__init__.py +1 -0
  196. parrot/handlers/agents/abstract.py +900 -0
  197. parrot/handlers/bots.py +338 -0
  198. parrot/handlers/chat.py +915 -0
  199. parrot/handlers/creation.sql +192 -0
  200. parrot/handlers/crew/ARCHITECTURE.md +362 -0
  201. parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
  202. parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
  203. parrot/handlers/crew/__init__.py +0 -0
  204. parrot/handlers/crew/handler.py +801 -0
  205. parrot/handlers/crew/models.py +229 -0
  206. parrot/handlers/crew/redis_persistence.py +523 -0
  207. parrot/handlers/jobs/__init__.py +10 -0
  208. parrot/handlers/jobs/job.py +384 -0
  209. parrot/handlers/jobs/mixin.py +627 -0
  210. parrot/handlers/jobs/models.py +115 -0
  211. parrot/handlers/jobs/worker.py +31 -0
  212. parrot/handlers/models.py +596 -0
  213. parrot/handlers/o365_auth.py +105 -0
  214. parrot/handlers/stream.py +337 -0
  215. parrot/interfaces/__init__.py +6 -0
  216. parrot/interfaces/aws.py +143 -0
  217. parrot/interfaces/credentials.py +113 -0
  218. parrot/interfaces/database.py +27 -0
  219. parrot/interfaces/google.py +1123 -0
  220. parrot/interfaces/hierarchy.py +1227 -0
  221. parrot/interfaces/http.py +651 -0
  222. parrot/interfaces/images/__init__.py +0 -0
  223. parrot/interfaces/images/plugins/__init__.py +24 -0
  224. parrot/interfaces/images/plugins/abstract.py +58 -0
  225. parrot/interfaces/images/plugins/analisys.py +148 -0
  226. parrot/interfaces/images/plugins/classify.py +150 -0
  227. parrot/interfaces/images/plugins/classifybase.py +182 -0
  228. parrot/interfaces/images/plugins/detect.py +150 -0
  229. parrot/interfaces/images/plugins/exif.py +1103 -0
  230. parrot/interfaces/images/plugins/hash.py +52 -0
  231. parrot/interfaces/images/plugins/vision.py +104 -0
  232. parrot/interfaces/images/plugins/yolo.py +66 -0
  233. parrot/interfaces/images/plugins/zerodetect.py +197 -0
  234. parrot/interfaces/o365.py +978 -0
  235. parrot/interfaces/onedrive.py +822 -0
  236. parrot/interfaces/sharepoint.py +1435 -0
  237. parrot/interfaces/soap.py +257 -0
  238. parrot/loaders/__init__.py +8 -0
  239. parrot/loaders/abstract.py +1131 -0
  240. parrot/loaders/audio.py +199 -0
  241. parrot/loaders/basepdf.py +53 -0
  242. parrot/loaders/basevideo.py +1568 -0
  243. parrot/loaders/csv.py +409 -0
  244. parrot/loaders/docx.py +116 -0
  245. parrot/loaders/epubloader.py +316 -0
  246. parrot/loaders/excel.py +199 -0
  247. parrot/loaders/factory.py +55 -0
  248. parrot/loaders/files/__init__.py +0 -0
  249. parrot/loaders/files/abstract.py +39 -0
  250. parrot/loaders/files/html.py +26 -0
  251. parrot/loaders/files/text.py +63 -0
  252. parrot/loaders/html.py +152 -0
  253. parrot/loaders/markdown.py +442 -0
  254. parrot/loaders/pdf.py +373 -0
  255. parrot/loaders/pdfmark.py +320 -0
  256. parrot/loaders/pdftables.py +506 -0
  257. parrot/loaders/ppt.py +476 -0
  258. parrot/loaders/qa.py +63 -0
  259. parrot/loaders/splitters/__init__.py +10 -0
  260. parrot/loaders/splitters/base.py +138 -0
  261. parrot/loaders/splitters/md.py +228 -0
  262. parrot/loaders/splitters/token.py +143 -0
  263. parrot/loaders/txt.py +26 -0
  264. parrot/loaders/video.py +89 -0
  265. parrot/loaders/videolocal.py +218 -0
  266. parrot/loaders/videounderstanding.py +377 -0
  267. parrot/loaders/vimeo.py +167 -0
  268. parrot/loaders/web.py +599 -0
  269. parrot/loaders/youtube.py +504 -0
  270. parrot/manager/__init__.py +5 -0
  271. parrot/manager/manager.py +1030 -0
  272. parrot/mcp/__init__.py +28 -0
  273. parrot/mcp/adapter.py +105 -0
  274. parrot/mcp/cli.py +174 -0
  275. parrot/mcp/client.py +119 -0
  276. parrot/mcp/config.py +75 -0
  277. parrot/mcp/integration.py +842 -0
  278. parrot/mcp/oauth.py +933 -0
  279. parrot/mcp/server.py +225 -0
  280. parrot/mcp/transports/__init__.py +3 -0
  281. parrot/mcp/transports/base.py +279 -0
  282. parrot/mcp/transports/grpc_session.py +163 -0
  283. parrot/mcp/transports/http.py +312 -0
  284. parrot/mcp/transports/mcp.proto +108 -0
  285. parrot/mcp/transports/quic.py +1082 -0
  286. parrot/mcp/transports/sse.py +330 -0
  287. parrot/mcp/transports/stdio.py +309 -0
  288. parrot/mcp/transports/unix.py +395 -0
  289. parrot/mcp/transports/websocket.py +547 -0
  290. parrot/memory/__init__.py +16 -0
  291. parrot/memory/abstract.py +209 -0
  292. parrot/memory/agent.py +32 -0
  293. parrot/memory/cache.py +175 -0
  294. parrot/memory/core.py +555 -0
  295. parrot/memory/file.py +153 -0
  296. parrot/memory/mem.py +131 -0
  297. parrot/memory/redis.py +613 -0
  298. parrot/models/__init__.py +46 -0
  299. parrot/models/basic.py +118 -0
  300. parrot/models/compliance.py +208 -0
  301. parrot/models/crew.py +395 -0
  302. parrot/models/detections.py +654 -0
  303. parrot/models/generation.py +85 -0
  304. parrot/models/google.py +223 -0
  305. parrot/models/groq.py +23 -0
  306. parrot/models/openai.py +30 -0
  307. parrot/models/outputs.py +285 -0
  308. parrot/models/responses.py +938 -0
  309. parrot/notifications/__init__.py +743 -0
  310. parrot/openapi/__init__.py +3 -0
  311. parrot/openapi/components.yaml +641 -0
  312. parrot/openapi/config.py +322 -0
  313. parrot/outputs/__init__.py +32 -0
  314. parrot/outputs/formats/__init__.py +108 -0
  315. parrot/outputs/formats/altair.py +359 -0
  316. parrot/outputs/formats/application.py +122 -0
  317. parrot/outputs/formats/base.py +351 -0
  318. parrot/outputs/formats/bokeh.py +356 -0
  319. parrot/outputs/formats/card.py +424 -0
  320. parrot/outputs/formats/chart.py +436 -0
  321. parrot/outputs/formats/d3.py +255 -0
  322. parrot/outputs/formats/echarts.py +310 -0
  323. parrot/outputs/formats/generators/__init__.py +0 -0
  324. parrot/outputs/formats/generators/abstract.py +61 -0
  325. parrot/outputs/formats/generators/panel.py +145 -0
  326. parrot/outputs/formats/generators/streamlit.py +86 -0
  327. parrot/outputs/formats/generators/terminal.py +63 -0
  328. parrot/outputs/formats/holoviews.py +310 -0
  329. parrot/outputs/formats/html.py +147 -0
  330. parrot/outputs/formats/jinja2.py +46 -0
  331. parrot/outputs/formats/json.py +87 -0
  332. parrot/outputs/formats/map.py +933 -0
  333. parrot/outputs/formats/markdown.py +172 -0
  334. parrot/outputs/formats/matplotlib.py +237 -0
  335. parrot/outputs/formats/mixins/__init__.py +0 -0
  336. parrot/outputs/formats/mixins/emaps.py +855 -0
  337. parrot/outputs/formats/plotly.py +341 -0
  338. parrot/outputs/formats/seaborn.py +310 -0
  339. parrot/outputs/formats/table.py +397 -0
  340. parrot/outputs/formats/template_report.py +138 -0
  341. parrot/outputs/formats/yaml.py +125 -0
  342. parrot/outputs/formatter.py +152 -0
  343. parrot/outputs/templates/__init__.py +95 -0
  344. parrot/pipelines/__init__.py +0 -0
  345. parrot/pipelines/abstract.py +210 -0
  346. parrot/pipelines/detector.py +124 -0
  347. parrot/pipelines/models.py +90 -0
  348. parrot/pipelines/planogram.py +3002 -0
  349. parrot/pipelines/table.sql +97 -0
  350. parrot/plugins/__init__.py +106 -0
  351. parrot/plugins/importer.py +80 -0
  352. parrot/py.typed +0 -0
  353. parrot/registry/__init__.py +18 -0
  354. parrot/registry/registry.py +594 -0
  355. parrot/scheduler/__init__.py +1189 -0
  356. parrot/scheduler/models.py +60 -0
  357. parrot/security/__init__.py +16 -0
  358. parrot/security/prompt_injection.py +268 -0
  359. parrot/security/security_events.sql +25 -0
  360. parrot/services/__init__.py +1 -0
  361. parrot/services/mcp/__init__.py +8 -0
  362. parrot/services/mcp/config.py +13 -0
  363. parrot/services/mcp/server.py +295 -0
  364. parrot/services/o365_remote_auth.py +235 -0
  365. parrot/stores/__init__.py +7 -0
  366. parrot/stores/abstract.py +352 -0
  367. parrot/stores/arango.py +1090 -0
  368. parrot/stores/bigquery.py +1377 -0
  369. parrot/stores/cache.py +106 -0
  370. parrot/stores/empty.py +10 -0
  371. parrot/stores/faiss_store.py +1157 -0
  372. parrot/stores/kb/__init__.py +9 -0
  373. parrot/stores/kb/abstract.py +68 -0
  374. parrot/stores/kb/cache.py +165 -0
  375. parrot/stores/kb/doc.py +325 -0
  376. parrot/stores/kb/hierarchy.py +346 -0
  377. parrot/stores/kb/local.py +457 -0
  378. parrot/stores/kb/prompt.py +28 -0
  379. parrot/stores/kb/redis.py +659 -0
  380. parrot/stores/kb/store.py +115 -0
  381. parrot/stores/kb/user.py +374 -0
  382. parrot/stores/models.py +59 -0
  383. parrot/stores/pgvector.py +3 -0
  384. parrot/stores/postgres.py +2853 -0
  385. parrot/stores/utils/__init__.py +0 -0
  386. parrot/stores/utils/chunking.py +197 -0
  387. parrot/telemetry/__init__.py +3 -0
  388. parrot/telemetry/mixin.py +111 -0
  389. parrot/template/__init__.py +3 -0
  390. parrot/template/engine.py +259 -0
  391. parrot/tools/__init__.py +23 -0
  392. parrot/tools/abstract.py +644 -0
  393. parrot/tools/agent.py +363 -0
  394. parrot/tools/arangodbsearch.py +537 -0
  395. parrot/tools/arxiv_tool.py +188 -0
  396. parrot/tools/calculator/__init__.py +3 -0
  397. parrot/tools/calculator/operations/__init__.py +38 -0
  398. parrot/tools/calculator/operations/calculus.py +80 -0
  399. parrot/tools/calculator/operations/statistics.py +76 -0
  400. parrot/tools/calculator/tool.py +150 -0
  401. parrot/tools/cloudwatch.py +988 -0
  402. parrot/tools/codeinterpreter/__init__.py +127 -0
  403. parrot/tools/codeinterpreter/executor.py +371 -0
  404. parrot/tools/codeinterpreter/internals.py +473 -0
  405. parrot/tools/codeinterpreter/models.py +643 -0
  406. parrot/tools/codeinterpreter/prompts.py +224 -0
  407. parrot/tools/codeinterpreter/tool.py +664 -0
  408. parrot/tools/company_info/__init__.py +6 -0
  409. parrot/tools/company_info/tool.py +1138 -0
  410. parrot/tools/correlationanalysis.py +437 -0
  411. parrot/tools/database/abstract.py +286 -0
  412. parrot/tools/database/bq.py +115 -0
  413. parrot/tools/database/cache.py +284 -0
  414. parrot/tools/database/models.py +95 -0
  415. parrot/tools/database/pg.py +343 -0
  416. parrot/tools/databasequery.py +1159 -0
  417. parrot/tools/db.py +1800 -0
  418. parrot/tools/ddgo.py +370 -0
  419. parrot/tools/decorators.py +271 -0
  420. parrot/tools/dftohtml.py +282 -0
  421. parrot/tools/document.py +549 -0
  422. parrot/tools/ecs.py +819 -0
  423. parrot/tools/edareport.py +368 -0
  424. parrot/tools/elasticsearch.py +1049 -0
  425. parrot/tools/employees.py +462 -0
  426. parrot/tools/epson/__init__.py +96 -0
  427. parrot/tools/excel.py +683 -0
  428. parrot/tools/file/__init__.py +13 -0
  429. parrot/tools/file/abstract.py +76 -0
  430. parrot/tools/file/gcs.py +378 -0
  431. parrot/tools/file/local.py +284 -0
  432. parrot/tools/file/s3.py +511 -0
  433. parrot/tools/file/tmp.py +309 -0
  434. parrot/tools/file/tool.py +501 -0
  435. parrot/tools/file_reader.py +129 -0
  436. parrot/tools/flowtask/__init__.py +19 -0
  437. parrot/tools/flowtask/tool.py +761 -0
  438. parrot/tools/gittoolkit.py +508 -0
  439. parrot/tools/google/__init__.py +18 -0
  440. parrot/tools/google/base.py +169 -0
  441. parrot/tools/google/tools.py +1251 -0
  442. parrot/tools/googlelocation.py +5 -0
  443. parrot/tools/googleroutes.py +5 -0
  444. parrot/tools/googlesearch.py +5 -0
  445. parrot/tools/googlesitesearch.py +5 -0
  446. parrot/tools/googlevoice.py +2 -0
  447. parrot/tools/gvoice.py +695 -0
  448. parrot/tools/ibisworld/README.md +225 -0
  449. parrot/tools/ibisworld/__init__.py +11 -0
  450. parrot/tools/ibisworld/tool.py +366 -0
  451. parrot/tools/jiratoolkit.py +1718 -0
  452. parrot/tools/manager.py +1098 -0
  453. parrot/tools/math.py +152 -0
  454. parrot/tools/metadata.py +476 -0
  455. parrot/tools/msteams.py +1621 -0
  456. parrot/tools/msword.py +635 -0
  457. parrot/tools/multidb.py +580 -0
  458. parrot/tools/multistoresearch.py +369 -0
  459. parrot/tools/networkninja.py +167 -0
  460. parrot/tools/nextstop/__init__.py +4 -0
  461. parrot/tools/nextstop/base.py +286 -0
  462. parrot/tools/nextstop/employee.py +733 -0
  463. parrot/tools/nextstop/store.py +462 -0
  464. parrot/tools/notification.py +435 -0
  465. parrot/tools/o365/__init__.py +42 -0
  466. parrot/tools/o365/base.py +295 -0
  467. parrot/tools/o365/bundle.py +522 -0
  468. parrot/tools/o365/events.py +554 -0
  469. parrot/tools/o365/mail.py +992 -0
  470. parrot/tools/o365/onedrive.py +497 -0
  471. parrot/tools/o365/sharepoint.py +641 -0
  472. parrot/tools/openapi_toolkit.py +904 -0
  473. parrot/tools/openweather.py +527 -0
  474. parrot/tools/pdfprint.py +1001 -0
  475. parrot/tools/powerbi.py +518 -0
  476. parrot/tools/powerpoint.py +1113 -0
  477. parrot/tools/pricestool.py +146 -0
  478. parrot/tools/products/__init__.py +246 -0
  479. parrot/tools/prophet_tool.py +171 -0
  480. parrot/tools/pythonpandas.py +630 -0
  481. parrot/tools/pythonrepl.py +910 -0
  482. parrot/tools/qsource.py +436 -0
  483. parrot/tools/querytoolkit.py +395 -0
  484. parrot/tools/quickeda.py +827 -0
  485. parrot/tools/resttool.py +553 -0
  486. parrot/tools/retail/__init__.py +0 -0
  487. parrot/tools/retail/bby.py +528 -0
  488. parrot/tools/sandboxtool.py +703 -0
  489. parrot/tools/sassie/__init__.py +352 -0
  490. parrot/tools/scraping/__init__.py +7 -0
  491. parrot/tools/scraping/docs/select.md +466 -0
  492. parrot/tools/scraping/documentation.md +1278 -0
  493. parrot/tools/scraping/driver.py +436 -0
  494. parrot/tools/scraping/models.py +576 -0
  495. parrot/tools/scraping/options.py +85 -0
  496. parrot/tools/scraping/orchestrator.py +517 -0
  497. parrot/tools/scraping/readme.md +740 -0
  498. parrot/tools/scraping/tool.py +3115 -0
  499. parrot/tools/seasonaldetection.py +642 -0
  500. parrot/tools/shell_tool/__init__.py +5 -0
  501. parrot/tools/shell_tool/actions.py +408 -0
  502. parrot/tools/shell_tool/engine.py +155 -0
  503. parrot/tools/shell_tool/models.py +322 -0
  504. parrot/tools/shell_tool/tool.py +442 -0
  505. parrot/tools/site_search.py +214 -0
  506. parrot/tools/textfile.py +418 -0
  507. parrot/tools/think.py +378 -0
  508. parrot/tools/toolkit.py +298 -0
  509. parrot/tools/webapp_tool.py +187 -0
  510. parrot/tools/whatif.py +1279 -0
  511. parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
  512. parrot/tools/workday/__init__.py +6 -0
  513. parrot/tools/workday/models.py +1389 -0
  514. parrot/tools/workday/tool.py +1293 -0
  515. parrot/tools/yfinance_tool.py +306 -0
  516. parrot/tools/zipcode.py +217 -0
  517. parrot/utils/__init__.py +2 -0
  518. parrot/utils/helpers.py +73 -0
  519. parrot/utils/parsers/__init__.py +5 -0
  520. parrot/utils/parsers/toml.c +12078 -0
  521. parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
  522. parrot/utils/parsers/toml.pyx +21 -0
  523. parrot/utils/toml.py +11 -0
  524. parrot/utils/types.cpp +20936 -0
  525. parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
  526. parrot/utils/types.pyx +213 -0
  527. parrot/utils/uv.py +11 -0
  528. parrot/version.py +10 -0
  529. parrot/yaml-rs/Cargo.lock +350 -0
  530. parrot/yaml-rs/Cargo.toml +19 -0
  531. parrot/yaml-rs/pyproject.toml +19 -0
  532. parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
  533. parrot/yaml-rs/src/lib.rs +222 -0
  534. requirements/docker-compose.yml +24 -0
  535. requirements/requirements-dev.txt +21 -0
@@ -0,0 +1,1001 @@
1
+ """
2
+ Enhanced PDF Print Tool with improved Markdown table support.
3
+ """
4
+ import re
5
+ import logging
6
+ from typing import Any, Dict, List, Optional
7
+ import asyncio
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ import traceback
11
+ import tiktoken
12
+ from jinja2 import Environment, FileSystemLoader
13
+ from pydantic import BaseModel, Field, field_validator
14
+ import markdown
15
+ from weasyprint import HTML, CSS
16
+ from .abstract import AbstractTool
17
+
18
+
19
+ # Suppress various library warnings
20
+ logging.getLogger("weasyprint").setLevel(logging.ERROR)
21
+ logging.getLogger("tiktoken").setLevel(logging.ERROR)
22
+ logging.getLogger("MARKDOWN").setLevel(logging.ERROR)
23
+ logging.getLogger("fontTools.ttLib.ttFont").setLevel(logging.ERROR)
24
+ logging.getLogger("fontTools.subset.timer").setLevel(logging.ERROR)
25
+ logging.getLogger("fontTools.subset").setLevel(logging.ERROR)
26
+
27
+
28
+ def count_tokens(text: str, model: str = "gpt-4") -> int:
29
+ """Count tokens in text using tiktoken."""
30
+ try:
31
+ enc = tiktoken.encoding_for_model(model)
32
+ return len(enc.encode(text))
33
+ except Exception:
34
+ # Fallback to rough character estimation
35
+ return len(text) // 4
36
+
37
+
38
+ class PDFPrintArgs(BaseModel):
39
+ """Arguments schema for PDFPrintTool."""
40
+
41
+ text: str = Field(
42
+ ...,
43
+ description="The text content (plaintext or Markdown) to convert to PDF"
44
+ )
45
+ file_prefix: str = Field(
46
+ "document",
47
+ description="Prefix for the output filename (timestamp and extension added automatically)"
48
+ )
49
+ template_name: Optional[str] = Field(
50
+ None,
51
+ description="Name of the HTML template to use (e.g., 'report.html'). If None, uses default template"
52
+ )
53
+ template_vars: Optional[Dict[str, Any]] = Field(
54
+ None,
55
+ description="Dictionary of variables to pass to the template (e.g., title, author, date)"
56
+ )
57
+ stylesheets: Optional[List[str]] = Field(
58
+ None,
59
+ description="List of CSS file paths (relative to templates directory) to apply"
60
+ )
61
+ auto_detect_markdown: bool = Field(
62
+ True,
63
+ description="Whether to automatically detect and convert Markdown content to HTML"
64
+ )
65
+
66
+ @field_validator('text')
67
+ @classmethod
68
+ def validate_text(cls, v):
69
+ if not v or not v.strip():
70
+ raise ValueError("Text content cannot be empty")
71
+ return v
72
+
73
+ @field_validator('file_prefix')
74
+ @classmethod
75
+ def validate_file_prefix(cls, v):
76
+ # Remove invalid filename characters
77
+ if v:
78
+ v = re.sub(r'[<>:"/\\|?*]', '_', v)
79
+ return v or "document"
80
+
81
+ @field_validator('template_name')
82
+ @classmethod
83
+ def validate_template_name(cls, v):
84
+ if v and not v.endswith('.html'):
85
+ v = f"{v}.html"
86
+ return v
87
+
88
+
89
+ class PDFPrintTool(AbstractTool):
90
+ """
91
+ Enhanced PDF Print Tool with improved Markdown table support.
92
+
93
+ This tool processes both plain text and Markdown content, with special
94
+ attention to proper table rendering in PDF output.
95
+ """
96
+
97
+ name = "pdf_print"
98
+ description = (
99
+ "Generate PDF documents from text content. Supports both plain text and Markdown "
100
+ "with enhanced table rendering. Can use custom HTML templates and CSS styling."
101
+ )
102
+ args_schema = PDFPrintArgs
103
+
104
+ def __init__(
105
+ self,
106
+ templates_dir: Optional[Path] = None,
107
+ default_template: str = "report.html",
108
+ default_stylesheets: Optional[List[str]] = None,
109
+ **kwargs
110
+ ):
111
+ """Initialize the PDF Print Tool with enhanced table support."""
112
+ super().__init__(**kwargs)
113
+
114
+ # Set up templates directory
115
+ if templates_dir is None:
116
+ possible_paths = [
117
+ Path.cwd() / "templates",
118
+ Path(__file__).parent.parent / "templates",
119
+ self.static_dir / "templates" if self.static_dir else None
120
+ ]
121
+
122
+ for path in possible_paths:
123
+ if path and path.exists():
124
+ templates_dir = path
125
+ break
126
+
127
+ if templates_dir is None:
128
+ templates_dir = self.static_dir / "templates" if self.static_dir else Path("templates")
129
+ templates_dir.mkdir(parents=True, exist_ok=True)
130
+ self._create_default_template(templates_dir)
131
+
132
+ self.templates_dir = Path(templates_dir)
133
+ self.default_template = default_template
134
+ self.default_stylesheets = default_stylesheets or ["css/base.css"]
135
+
136
+ # Initialize Jinja2 environment
137
+ try:
138
+ self.env = Environment(
139
+ loader=FileSystemLoader(str(self.templates_dir)),
140
+ autoescape=True
141
+ )
142
+ self.logger.info(
143
+ f"PDF Print tool initialized with templates from: {self.templates_dir}"
144
+ )
145
+ except Exception as e:
146
+ self.logger.error(f"Error initializing Jinja2 environment: {e}")
147
+ raise ValueError(f"Failed to initialize PDF tool: {e}")
148
+
149
+ def _default_output_dir(self) -> Path:
150
+ """Get the default output directory for PDF files."""
151
+ return self.static_dir / "documents" / "pdf"
152
+
153
+ def _create_default_template(self, templates_dir: Path) -> None:
154
+ """Create a default HTML template with enhanced table styling."""
155
+ try:
156
+ # Create directories
157
+ (templates_dir / "css").mkdir(parents=True, exist_ok=True)
158
+
159
+ # Default HTML template
160
+ default_html = """<!DOCTYPE html>
161
+ <html lang="en">
162
+ <head>
163
+ <meta charset="UTF-8">
164
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
165
+ <title>{{ title | default('Document') }}</title>
166
+ </head>
167
+ <body>
168
+ <header>
169
+ <h1>{{ title | default('Document') }}</h1>
170
+ {% if author %}<p class="author">By: {{ author }}</p>{% endif %}
171
+ {% if date %}<p class="date">{{ date }}</p>{% endif %}
172
+ </header>
173
+
174
+ <main>
175
+ {{ body | safe }}
176
+ </main>
177
+
178
+ <footer>
179
+ <p>Generated on {{ generated_date | default('') }}</p>
180
+ </footer>
181
+ </body>
182
+ </html>"""
183
+
184
+ # Enhanced CSS with better table styling
185
+ default_css = """
186
+ body {
187
+ font-family: 'Arial', sans-serif;
188
+ line-height: 1.6;
189
+ margin: 2cm;
190
+ color: #333;
191
+ }
192
+
193
+ header {
194
+ border-bottom: 2px solid #333;
195
+ margin-bottom: 2em;
196
+ padding-bottom: 1em;
197
+ }
198
+
199
+ h1 {
200
+ color: #2c3e50;
201
+ font-size: 2.5em;
202
+ margin-bottom: 0.5em;
203
+ }
204
+
205
+ h2 {
206
+ color: #34495e;
207
+ font-size: 2em;
208
+ margin-top: 1.5em;
209
+ page-break-after: avoid;
210
+ }
211
+
212
+ h3 {
213
+ color: #7f8c8d;
214
+ font-size: 1.5em;
215
+ margin-top: 1.2em;
216
+ page-break-after: avoid;
217
+ }
218
+
219
+ .author, .date {
220
+ font-style: italic;
221
+ color: #7f8c8d;
222
+ margin: 0.5em 0;
223
+ }
224
+
225
+ /* Enhanced table styling */
226
+ table {
227
+ border-collapse: collapse;
228
+ width: 100%;
229
+ margin: 1.5em 0;
230
+ background-color: white;
231
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
232
+ page-break-inside: avoid;
233
+ }
234
+
235
+ th, td {
236
+ border: 1px solid #ddd;
237
+ padding: 12px 8px;
238
+ text-align: left;
239
+ vertical-align: top;
240
+ }
241
+
242
+ th {
243
+ background-color: #f8f9fa;
244
+ font-weight: bold;
245
+ color: #2c3e50;
246
+ border-bottom: 2px solid #34495e;
247
+ }
248
+
249
+ tbody tr:nth-child(even) {
250
+ background-color: #f8f9fa;
251
+ }
252
+
253
+ tbody tr:hover {
254
+ background-color: #e3f2fd;
255
+ }
256
+
257
+ /* Responsive table */
258
+ @media screen and (max-width: 768px) {
259
+ table {
260
+ font-size: 0.9em;
261
+ }
262
+
263
+ th, td {
264
+ padding: 8px 4px;
265
+ }
266
+ }
267
+
268
+ /* Number alignment */
269
+ td[align="right"],
270
+ th[align="right"],
271
+ .number {
272
+ text-align: right;
273
+ }
274
+
275
+ /* Code styling */
276
+ code {
277
+ background-color: #f4f4f4;
278
+ padding: 2px 4px;
279
+ border-radius: 3px;
280
+ font-family: 'Courier New', monospace;
281
+ font-size: 0.9em;
282
+ }
283
+
284
+ pre {
285
+ background-color: #f4f4f4;
286
+ padding: 1em;
287
+ border-radius: 5px;
288
+ overflow-x: auto;
289
+ border-left: 4px solid #3498db;
290
+ }
291
+
292
+ pre code {
293
+ background-color: transparent;
294
+ padding: 0;
295
+ }
296
+
297
+ blockquote {
298
+ border-left: 4px solid #3498db;
299
+ margin: 1em 0;
300
+ padding-left: 1em;
301
+ font-style: italic;
302
+ background-color: #f8f9fa;
303
+ padding: 1em 1em 1em 2em;
304
+ }
305
+
306
+ ul, ol {
307
+ margin: 1em 0;
308
+ padding-left: 2em;
309
+ }
310
+
311
+ li {
312
+ margin: 0.5em 0;
313
+ }
314
+
315
+ footer {
316
+ border-top: 1px solid #ddd;
317
+ margin-top: 2em;
318
+ padding-top: 1em;
319
+ font-size: 0.9em;
320
+ color: #7f8c8d;
321
+ }
322
+
323
+ /* Print specific styles */
324
+ @media print {
325
+ body {
326
+ margin: 1cm;
327
+ }
328
+
329
+ header {
330
+ page-break-after: avoid;
331
+ }
332
+
333
+ h1, h2, h3 {
334
+ page-break-after: avoid;
335
+ }
336
+
337
+ table {
338
+ page-break-inside: avoid;
339
+ }
340
+
341
+ tr {
342
+ page-break-inside: avoid;
343
+ }
344
+
345
+ th {
346
+ background-color: #f0f0f0 !important;
347
+ -webkit-print-color-adjust: exact;
348
+ color-adjust: exact;
349
+ }
350
+
351
+ tbody tr:nth-child(even) {
352
+ background-color: #f8f8f8 !important;
353
+ -webkit-print-color-adjust: exact;
354
+ color-adjust: exact;
355
+ }
356
+ }
357
+ """
358
+ # Write files
359
+ with open(templates_dir / "report.html", 'w', encoding='utf-8') as f:
360
+ f.write(default_html)
361
+
362
+ with open(templates_dir / "css" / "base.css", 'w', encoding='utf-8') as f:
363
+ f.write(default_css)
364
+
365
+ self.logger.info("Created default template files with enhanced table support")
366
+
367
+ except Exception as e:
368
+ self.logger.error(f"Error creating default template: {e}")
369
+
370
+ def _is_markdown(self, text: str) -> bool:
371
+ """Enhanced Markdown detection including table patterns."""
372
+ if not text or not isinstance(text, str):
373
+ return False
374
+
375
+ text = text.strip()
376
+ if not text:
377
+ return False
378
+
379
+ # Check first character for Markdown markers
380
+ first_char = text[0]
381
+ if first_char in "#*_>`-":
382
+ return True
383
+
384
+ # Check if first character is a digit (for numbered lists)
385
+ if first_char.isdigit() and re.match(r'^\d+\.', text):
386
+ return True
387
+
388
+ # Enhanced Markdown patterns including tables
389
+ markdown_patterns = [
390
+ r"#{1,6}\s+", # Headers
391
+ r"\*\*.*?\*\*", # Bold
392
+ r"__.*?__", # Bold alternative
393
+ r"\*.*?\*", # Italic
394
+ r"_.*?_", # Italic alternative
395
+ r"`.*?`", # Inline code
396
+ r"\[.*?\]\(.*?\)", # Links
397
+ r"^\s*[\*\-\+]\s+", # Unordered lists
398
+ r"^\s*\d+\.\s+", # Ordered lists
399
+ r"```.*?```", # Code blocks
400
+ r"^\s*>\s+", # Blockquotes
401
+ r"^\s*\|.*\|.*$", # Table rows
402
+ r"^\s*\|[-\s:|]+\|.*$", # Table separator rows
403
+ r"^\s*\|[\-\s]+\|[\-\s\|]*$", # ASCII-style table separators
404
+ ]
405
+
406
+ for pattern in markdown_patterns:
407
+ if re.search(pattern, text, re.MULTILINE | re.DOTALL):
408
+ return True
409
+
410
+ return False
411
+
412
+ def _preprocess_markdown_tables(self, text: str) -> str:
413
+ """
414
+ Preprocess Markdown tables to ensure proper formatting.
415
+
416
+ This function fixes common table formatting issues and ensures
417
+ tables are properly recognized by the Markdown parser.
418
+ """
419
+ lines = text.split('\n')
420
+ processed_lines = []
421
+ in_table = False
422
+ table_buffer = []
423
+
424
+ for i, line in enumerate(lines):
425
+ stripped = line.strip()
426
+
427
+ # Detect potential table rows
428
+ if stripped and '|' in stripped:
429
+ # Check if this is an ASCII-style table separator with many dashes
430
+ if re.match(r'^\s*\|[\-\s]+\|[\-\s\|]*$', stripped):
431
+ # Convert ASCII separator to Markdown format
432
+ # Count the number of columns from the previous line
433
+ if table_buffer:
434
+ prev_line = table_buffer[-1]
435
+ col_count = prev_line.count('|') - 1
436
+ markdown_separator = '|' + '---|' * col_count
437
+ table_buffer.append(markdown_separator)
438
+ else:
439
+ # Fallback separator
440
+ table_buffer.append('|---|---|---|')
441
+ in_table = True
442
+ continue
443
+
444
+ # Check if this looks like a table row (starts and ends with |)
445
+ if stripped.startswith('|') and stripped.endswith('|'):
446
+ # Clean up the row - remove extra spaces and normalize
447
+ cells = [cell.strip() for cell in stripped.split('|')[1:-1]]
448
+ cleaned_row = '| ' + ' | '.join(cells) + ' |'
449
+ table_buffer.append(cleaned_row)
450
+ in_table = True
451
+ continue
452
+
453
+ # Check for table row without proper pipe formatting
454
+ if stripped.count('|') >= 2:
455
+ # Ensure the line starts and ends with pipes
456
+ if not stripped.startswith('|'):
457
+ stripped = '| ' + stripped
458
+ if not stripped.endswith('|'):
459
+ stripped = stripped + ' |'
460
+
461
+ # Clean up the row
462
+ cells = [cell.strip() for cell in stripped.split('|')[1:-1]]
463
+ cleaned_row = '| ' + ' | '.join(cells) + ' |'
464
+ table_buffer.append(cleaned_row)
465
+ in_table = True
466
+ continue
467
+
468
+ # If we were in a table and hit a non-table line
469
+ if in_table and not stripped:
470
+ # End of table - add the buffered table and empty line
471
+ if table_buffer:
472
+ processed_lines.extend(table_buffer)
473
+ processed_lines.append('') # Add empty line after table
474
+ table_buffer = []
475
+ in_table = False
476
+ processed_lines.append(line)
477
+ continue
478
+ elif in_table and stripped and '|' not in stripped:
479
+ # End of table - add the buffered table
480
+ if table_buffer:
481
+ processed_lines.extend(table_buffer)
482
+ processed_lines.append('') # Add empty line after table
483
+ table_buffer = []
484
+ in_table = False
485
+ processed_lines.append(line)
486
+ continue
487
+
488
+ # Not a table line
489
+ if not in_table:
490
+ processed_lines.append(line)
491
+
492
+ # Handle any remaining table buffer
493
+ if table_buffer:
494
+ processed_lines.extend(table_buffer)
495
+
496
+ return '\n'.join(processed_lines)
497
+
498
+ def _convert_ascii_tables_to_html(self, text: str) -> str:
499
+ """
500
+ Convert ASCII-style tables directly to HTML if Markdown conversion fails.
501
+ """
502
+ lines = text.split('\n')
503
+ result_lines = []
504
+ i = 0
505
+
506
+ while i < len(lines):
507
+ line = lines[i].strip()
508
+
509
+ # Look for potential table start (line with pipes)
510
+ if line and '|' in line and line.count('|') >= 2:
511
+ # Check if next line is a separator
512
+ table_lines = [line]
513
+ j = i + 1
514
+
515
+ # Collect all consecutive lines that look like table rows
516
+ while j < len(lines):
517
+ next_line = lines[j].strip()
518
+ if next_line and '|' in next_line:
519
+ table_lines.append(next_line)
520
+ j += 1
521
+ elif not next_line: # Empty line
522
+ j += 1
523
+ break
524
+ else:
525
+ break
526
+
527
+ # If we have at least 2 lines, try to convert to HTML table
528
+ if len(table_lines) >= 2:
529
+ html_table = self._ascii_to_html_table(table_lines)
530
+ if html_table:
531
+ result_lines.append(html_table)
532
+ i = j
533
+ continue
534
+
535
+ # Not a table line, add as-is
536
+ result_lines.append(lines[i])
537
+ i += 1
538
+
539
+ return '\n'.join(result_lines)
540
+
541
+ def _ascii_to_html_table(self, table_lines: List[str]) -> str:
542
+ """
543
+ Convert ASCII table lines to HTML table.
544
+ """
545
+ try:
546
+ # Remove empty lines and separator lines
547
+ data_lines = []
548
+ for line in table_lines:
549
+ if line.strip() and not re.match(r'^\s*\|[\-\s]+\|[\-\s\|]*$', line.strip()):
550
+ data_lines.append(line.strip())
551
+
552
+ if len(data_lines) < 1:
553
+ return ""
554
+
555
+ html_parts = ['<table class="ascii-table">']
556
+
557
+ # Process each line
558
+ for idx, line in enumerate(data_lines):
559
+ # Split by pipe and clean up
560
+ cells = [cell.strip() for cell in line.split('|')[1:-1]] # Remove first/last empty parts
561
+
562
+ if idx == 0:
563
+ # First row is header
564
+ html_parts.append('<thead><tr>')
565
+ for cell in cells:
566
+ html_parts.append(f'<th>{cell}</th>')
567
+ html_parts.append('</tr></thead><tbody>')
568
+ else:
569
+ # Data row
570
+ html_parts.append('<tr>')
571
+ for cell in cells:
572
+ # Check if cell content is numeric for right alignment
573
+ if re.match(r'^\s*\d+(?:\.\d+)?\s*$', cell):
574
+ html_parts.append(f'<td align="right">{cell}</td>')
575
+ else:
576
+ html_parts.append(f'<td>{cell}</td>')
577
+ html_parts.append('</tr>')
578
+
579
+ html_parts.append('</tbody></table>')
580
+ return '\n'.join(html_parts)
581
+
582
+ except Exception as e:
583
+ self.logger.warning(f"Failed to convert ASCII table to HTML: {e}")
584
+ return ""
585
+
586
+ def _post_process_html_tables(self, html_content: str) -> str:
587
+ """
588
+ Post-process HTML to improve table formatting.
589
+ """
590
+ # Add CSS classes to tables for better styling
591
+ html_content = re.sub(
592
+ r'<table>',
593
+ '<table class="markdown-table">',
594
+ html_content,
595
+ flags=re.IGNORECASE
596
+ )
597
+
598
+ # Ensure numeric columns are right-aligned
599
+ def align_numeric_cells(match):
600
+ cell_content = match.group(1)
601
+ # Check if content looks like a number
602
+ if re.match(r'^\s*\d+(?:\.\d+)?\s*$', cell_content.strip()):
603
+ return f'<td align="right">{cell_content}</td>'
604
+ return match.group(0)
605
+
606
+ html_content = re.sub(
607
+ r'<td>(.*?)</td>',
608
+ align_numeric_cells,
609
+ html_content,
610
+ flags=re.IGNORECASE | re.DOTALL
611
+ )
612
+
613
+ return html_content
614
+
615
+ def _process_content(
616
+ self,
617
+ text: str,
618
+ auto_detect_markdown: bool,
619
+ template_name: Optional[str],
620
+ template_vars: Optional[Dict[str, Any]]
621
+ ) -> str:
622
+ """Enhanced content processing with better table handling."""
623
+ content = text.strip()
624
+
625
+ # Convert Markdown to HTML if needed
626
+ if auto_detect_markdown and self._is_markdown(content):
627
+ self.logger.info("Detected Markdown content, converting to HTML")
628
+ try:
629
+ # Preprocess tables for better recognition
630
+ content = self._preprocess_markdown_tables(content)
631
+
632
+ # Configure markdown with all necessary extensions
633
+ md = markdown.Markdown(
634
+ extensions=[
635
+ 'tables', # Table support
636
+ 'fenced_code', # Code blocks
637
+ 'nl2br', # Newline to break
638
+ 'attr_list', # Attribute lists
639
+ 'def_list', # Definition lists
640
+ 'footnotes', # Footnotes
641
+ 'toc', # Table of contents
642
+ 'codehilite', # Code highlighting
643
+ 'extra' # Meta extension with many sub-extensions
644
+ ],
645
+ extension_configs={
646
+ 'tables': {
647
+ 'use_align_attribute': True
648
+ },
649
+ 'codehilite': {
650
+ 'css_class': 'highlight',
651
+ 'use_pygments': False
652
+ }
653
+ },
654
+ output_format='html5'
655
+ )
656
+
657
+ content = md.convert(content)
658
+
659
+ # If no tables were converted but we suspect there are ASCII tables, try fallback
660
+ if '<table' not in content and '|' in text and text.count('|') > 4:
661
+ self.logger.info("Markdown didn't create tables, trying ASCII table conversion")
662
+ content = self._convert_ascii_tables_to_html(text)
663
+
664
+ # Post-process HTML tables
665
+ content = self._post_process_html_tables(content)
666
+
667
+ self.logger.debug(f"Markdown converted with tables. Length: {len(content)}")
668
+
669
+ # Log table detection
670
+ table_count = content.count('<table')
671
+ if table_count > 0:
672
+ self.logger.info(f"Successfully converted {table_count} table(s) to HTML")
673
+ else:
674
+ self.logger.warning("No tables were detected in the conversion")
675
+
676
+ except Exception as e:
677
+ self.logger.warning(f"Markdown conversion failed: {e}, trying ASCII table conversion")
678
+ # Try ASCII table conversion as fallback
679
+ try:
680
+ content = self._convert_ascii_tables_to_html(content)
681
+ if '<table' not in content:
682
+ # Convert line breaks for plain text
683
+ content = content.replace('\n', '<br>')
684
+ except Exception as ascii_error:
685
+ self.logger.warning(f"ASCII table conversion also failed: {ascii_error}")
686
+ content = content.replace('\n', '<br>')
687
+
688
+ # Apply template if specified
689
+ if template_name:
690
+ try:
691
+ template = self.env.get_template(template_name)
692
+
693
+ # Prepare template context
694
+ context = {
695
+ "body": content,
696
+ "content": content,
697
+ "generated_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
698
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
699
+ **(template_vars or {})
700
+ }
701
+
702
+ content = template.render(**context)
703
+ self.logger.info(f"Applied template: {template_name}")
704
+
705
+ except Exception as e:
706
+ self.logger.error(f"Error applying template {template_name}: {e}")
707
+
708
+ # Create a simple HTML wrapper with table-friendly styling
709
+ title = template_vars.get('title', 'Document') if template_vars else 'Document'
710
+ author = template_vars.get('author', '') if template_vars else ''
711
+
712
+ content = f"""<!DOCTYPE html>
713
+ <html>
714
+ <head>
715
+ <meta charset="UTF-8">
716
+ <title>{title}</title>
717
+ <style>
718
+ body {{ font-family: Arial, sans-serif; margin: 2cm; line-height: 1.6; }}
719
+ table {{ border-collapse: collapse; width: 100%; margin: 1em 0; }}
720
+ th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
721
+ th {{ background-color: #f2f2f2; font-weight: bold; }}
722
+ h1, h2, h3 {{ color: #333; }}
723
+ </style>
724
+ </head>
725
+ <body>
726
+ <header>
727
+ <h1>{title}</h1>
728
+ {f'<p><em>By: {author}</em></p>' if author else ''}
729
+ <p><em>Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</em></p>
730
+ <hr>
731
+ </header>
732
+ <main>
733
+ {content}
734
+ </main>
735
+ </body>
736
+ </html>"""
737
+ self.logger.info("Applied simple HTML wrapper as template fallback")
738
+ else:
739
+ # No template specified - ensure we have a complete HTML document with table styling
740
+ if not content.strip().startswith('<!DOCTYPE') and not content.strip().startswith('<html'):
741
+ content = f"""<!DOCTYPE html>
742
+ <html>
743
+ <head>
744
+ <meta charset="UTF-8">
745
+ <title>Document</title>
746
+ <style>
747
+ body {{ font-family: Arial, sans-serif; margin: 2cm; line-height: 1.6; }}
748
+ table {{ border-collapse: collapse; width: 100%; margin: 1em 0; }}
749
+ th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
750
+ th {{ background-color: #f2f2f2; font-weight: bold; }}
751
+ tbody tr:nth-child(even) {{ background-color: #f8f9fa; }}
752
+ h1, h2, h3 {{ color: #333; }}
753
+ </style>
754
+ </head>
755
+ <body>
756
+ {content}
757
+ </body>
758
+ </html>"""
759
+ self.logger.info("Added basic HTML wrapper with table styling to content")
760
+
761
+ return content
762
+
763
+ def _load_stylesheets(self, stylesheets: Optional[List[str]]) -> List[CSS]:
764
+ """Load CSS stylesheets for PDF generation."""
765
+ css_objects = []
766
+
767
+ # Use provided stylesheets or defaults
768
+ css_files = stylesheets or self.default_stylesheets
769
+
770
+ for css_file in css_files:
771
+ try:
772
+ css_path = self.templates_dir / css_file
773
+ if css_path.exists():
774
+ css_objects.append(CSS(filename=str(css_path)))
775
+ self.logger.debug(f"Loaded stylesheet: {css_file}")
776
+ else:
777
+ self.logger.warning(f"Stylesheet not found: {css_path}")
778
+ except Exception as e:
779
+ self.logger.error(f"Error loading stylesheet {css_file}: {e}")
780
+
781
+ # Add base CSS if no stylesheets were loaded
782
+ if not css_objects:
783
+ try:
784
+ base_css_path = self.templates_dir / "css" / "base.css"
785
+ if base_css_path.exists():
786
+ css_objects.append(CSS(filename=str(base_css_path)))
787
+ self.logger.info("Added base.css as fallback stylesheet")
788
+ except Exception as e:
789
+ self.logger.error(f"Error loading base stylesheet: {e}")
790
+
791
+ return css_objects
792
+
793
+ async def _execute(
794
+ self,
795
+ text: str,
796
+ file_prefix: str = "document",
797
+ template_name: Optional[str] = None,
798
+ template_vars: Optional[Dict[str, Any]] = None,
799
+ stylesheets: Optional[List[str]] = None,
800
+ auto_detect_markdown: bool = True,
801
+ **kwargs
802
+ ) -> Dict[str, Any]:
803
+ """Execute PDF generation with enhanced table support."""
804
+ try:
805
+ self.logger.debug(
806
+ f"Starting PDF generation with {len(text)} characters of content"
807
+ )
808
+
809
+ # Process content with enhanced table handling
810
+ processed_content = self._process_content(
811
+ text, auto_detect_markdown, template_name, template_vars
812
+ )
813
+
814
+ # Log table information
815
+ table_count = processed_content.count('<table')
816
+ if table_count > 0:
817
+ self.logger.info(f"Content contains {table_count} HTML table(s)")
818
+
819
+ # Load stylesheets
820
+ css_objects = self._load_stylesheets(stylesheets)
821
+ self.logger.info(f"Loaded {len(css_objects)} CSS stylesheets")
822
+
823
+ # Generate filename and output path
824
+ output_filename = self.generate_filename(
825
+ prefix=file_prefix,
826
+ extension="pdf",
827
+ include_timestamp=True
828
+ )
829
+
830
+ # Ensure output directory exists
831
+ self.output_dir.mkdir(parents=True, exist_ok=True)
832
+ output_path = self.output_dir / output_filename
833
+ output_path = self.validate_output_path(output_path)
834
+
835
+ self.logger.info(f"Generating PDF: {output_path}")
836
+
837
+ # Debug: Save HTML content to file for inspection
838
+ debug_html_path = self.output_dir / f"{file_prefix}_debug.html"
839
+ try:
840
+ with open(debug_html_path, 'w', encoding='utf-8') as f:
841
+ f.write(processed_content)
842
+ self.logger.info(f"Debug HTML saved to: {debug_html_path}")
843
+ except Exception as e:
844
+ self.logger.warning(f"Could not save debug HTML: {e}")
845
+
846
+ # Generate PDF with enhanced error handling
847
+ try:
848
+ html_obj = HTML(
849
+ string=processed_content,
850
+ base_url=str(self.templates_dir)
851
+ )
852
+
853
+ # Generate PDF with print-friendly settings
854
+ html_obj.write_pdf(
855
+ str(output_path),
856
+ stylesheets=css_objects,
857
+ presentational_hints=True # This helps with table rendering
858
+ )
859
+
860
+ # Verify file creation
861
+ if not output_path.exists():
862
+ raise Exception("PDF file was not created")
863
+
864
+ file_size = output_path.stat().st_size
865
+ if file_size == 0:
866
+ raise Exception("PDF file is empty (0 bytes)")
867
+
868
+ self.logger.info(f"PDF generated successfully: {output_path} ({file_size} bytes)")
869
+
870
+ except Exception as pdf_error:
871
+ self.logger.error(f"PDF generation failed: {pdf_error}")
872
+ raise Exception(f"PDF generation failed: {pdf_error}")
873
+
874
+ # Generate URLs and results
875
+ file_url = self.to_static_url(output_path)
876
+ relative_url = self.relative_url(file_url)
877
+ token_count = count_tokens(text)
878
+ file_size = output_path.stat().st_size
879
+
880
+ result = {
881
+ "filename": output_filename,
882
+ "file_path": str(output_path),
883
+ "file_url": file_url,
884
+ "relative_url": relative_url,
885
+ "file_size": file_size,
886
+ "file_size_mb": round(file_size / (1024 * 1024), 2),
887
+ "content_stats": {
888
+ "characters": len(text),
889
+ "tokens": token_count,
890
+ "was_markdown": auto_detect_markdown and self._is_markdown(text),
891
+ "template_used": template_name or self.default_template,
892
+ "stylesheets_count": len(css_objects),
893
+ "tables_detected": processed_content.count('<table')
894
+ },
895
+ "generation_info": {
896
+ "timestamp": datetime.now().isoformat(),
897
+ "templates_dir": str(self.templates_dir),
898
+ "output_dir": str(self.output_dir),
899
+ "debug_html_path": str(debug_html_path) if 'debug_html_path' in locals() else None
900
+ }
901
+ }
902
+
903
+ self.logger.info(f"PDF generation completed: {file_size} bytes, {token_count} tokens, {result['content_stats']['tables_detected']} tables")
904
+ return result
905
+
906
+ except Exception as e:
907
+ self.logger.error(f"Error in PDF generation: {e}")
908
+ self.logger.error(traceback.format_exc())
909
+ raise
910
+
911
+ def execute_sync(
912
+ self,
913
+ text: str,
914
+ file_prefix: str = "document",
915
+ template_name: Optional[str] = None,
916
+ template_vars: Optional[Dict[str, Any]] = None,
917
+ stylesheets: Optional[List[str]] = None,
918
+ auto_detect_markdown: bool = True
919
+ ) -> Dict[str, Any]:
920
+ """
921
+ Execute PDF generation synchronously.
922
+
923
+ Args:
924
+ text: Text content to convert to PDF
925
+ file_prefix: Prefix for output filename
926
+ template_name: Optional HTML template name
927
+ template_vars: Optional template variables
928
+ stylesheets: Optional CSS stylesheets
929
+ auto_detect_markdown: Whether to auto-detect Markdown
930
+
931
+ Returns:
932
+ Dictionary with PDF generation results
933
+ """
934
+ try:
935
+ loop = asyncio.get_running_loop()
936
+ task = loop.create_task(self.execute(
937
+ text=text,
938
+ file_prefix=file_prefix,
939
+ template_name=template_name,
940
+ template_vars=template_vars,
941
+ stylesheets=stylesheets,
942
+ auto_detect_markdown=auto_detect_markdown
943
+ ))
944
+ return task
945
+ except RuntimeError:
946
+ return asyncio.run(self.execute(
947
+ text=text,
948
+ file_prefix=file_prefix,
949
+ template_name=template_name,
950
+ template_vars=template_vars,
951
+ stylesheets=stylesheets,
952
+ auto_detect_markdown=auto_detect_markdown
953
+ ))
954
+
955
+ def get_available_templates(self) -> List[str]:
956
+ """Get list of available HTML templates."""
957
+ try:
958
+ template_files = []
959
+ for file_path in self.templates_dir.glob("*.html"):
960
+ template_files.append(file_path.name)
961
+ return sorted(template_files)
962
+ except Exception as e:
963
+ self.logger.error(f"Error listing templates: {e}")
964
+ return []
965
+
966
+ def get_available_stylesheets(self) -> List[str]:
967
+ """Get list of available CSS stylesheets."""
968
+ try:
969
+ css_files = []
970
+ css_dir = self.templates_dir / "css"
971
+ if css_dir.exists():
972
+ for file_path in css_dir.glob("*.css"):
973
+ css_files.append(f"css/{file_path.name}")
974
+ return sorted(css_files)
975
+ except Exception as e:
976
+ self.logger.error(f"Error listing stylesheets: {e}")
977
+ return []
978
+
979
+ def preview_markdown(self, text: str) -> str:
980
+ """Convert Markdown to HTML for preview purposes."""
981
+ try:
982
+ if self._is_markdown(text):
983
+ # Use the same preprocessing for consistency
984
+ text = self._preprocess_markdown_tables(text)
985
+
986
+ html = markdown.markdown(
987
+ text,
988
+ extensions=['tables', 'fenced_code', 'toc', 'nl2br', 'extra'],
989
+ extension_configs={
990
+ 'tables': {
991
+ 'use_align_attribute': True
992
+ }
993
+ }
994
+ )
995
+
996
+ return self._post_process_html_tables(html)
997
+ else:
998
+ return f"<pre>{text}</pre>"
999
+ except Exception as e:
1000
+ self.logger.error(f"Error previewing markdown: {e}")
1001
+ return f"<p>Error previewing content: {e}</p>"