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,933 @@
1
+ from typing import Any, Optional, Tuple, Dict, Union, List
2
+ import re
3
+ import uuid
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ import html
7
+ import folium
8
+ import pandas as pd
9
+ from .chart import BaseChart
10
+ from . import register_renderer
11
+ from ...models.outputs import OutputMode
12
+
13
+ try:
14
+ import geopandas as gpd
15
+ GEOPANDAS_AVAILABLE = True
16
+ except ImportError:
17
+ GEOPANDAS_AVAILABLE = False
18
+ gpd = None
19
+
20
+
21
+ FOLIUM_SYSTEM_PROMPT = """FOLIUM MAP OUTPUT MODE:
22
+ When user request a MAP, generate an interactive map using Folium by extracting and using geographic information from the available data.
23
+
24
+ MAP GENERATION STRATEGY:
25
+ 1. First, create a filtered dataframe with the data you need (e.g., `map_data = df[...]`).
26
+ 2. The tool will confirm the variable creation.
27
+ 3. THEN, in the same or next turn, generate the Folium code referencing `map_data`.
28
+ 4. DO NOT print the content of `map_data`.
29
+ 5. ALWAYS return the map as Python code in a markdown block (```python).
30
+
31
+ 🚨 CRITICAL REQUIREMENTS: 🚨
32
+
33
+ 1. **ALWAYS analyze the available data for geographic information**:
34
+ - Look for columns with: latitude, longitude, lat, lon, coordinates
35
+ - Look for location columns: city, state, country, address, location, place
36
+ - Look for postal codes or zip codes that can be geocoded
37
+
38
+ 2. **Extract coordinates**:
39
+ - If you have lat/lon columns, use them directly
40
+ - If you have addresses/cities, you MUST extract or infer coordinates
41
+ - For well-known cities, use their standard coordinates
42
+ - Example: "Miami, Florida" → [25.7617, -80.1918]
43
+
44
+ 3. **Validate coordinates before using them**:
45
+ - NEVER use coordinates (0.0, 0.0) - these are invalid placeholders
46
+ - For invalid/missing coordinates:
47
+ * If you have an address, try to infer approximate location
48
+ * If location is in same city as other valid points, estimate nearby
49
+ * If no inference possible, SKIP the marker entirely
50
+ - Coordinate sanity checks:
51
+ * Latitude must be between -90 and 90
52
+ * Longitude must be between -180 and 180
53
+ * For US locations: lat ~25-50, lon ~-125 to -65
54
+
55
+ 4. **Return Python code in markdown block** (```python):
56
+ - Import folium at the top
57
+ - Store map in variable: 'm', 'map', 'folium_map', or 'my_map'
58
+ - Add ALL relevant data points as markers (except invalid ones)
59
+ - Include popups with useful information
60
+ - DO NOT call map.save() or display methods
61
+
62
+ 5. **Map configuration**:
63
+ - Center map on the average/median of all VALID locations
64
+ - Set appropriate zoom level (10-14 for cities, 5-8 for regions)
65
+ - Use descriptive popups and tooltips
66
+
67
+
68
+ BASIC EXAMPLE:
69
+ ```python
70
+ import folium
71
+
72
+ # Create base map with correct coordinate order
73
+ m = folium.Map(
74
+ location=[40.7128, -74.0060], # [latitude, longitude]
75
+ zoom_start=12,
76
+ tiles='OpenStreetMap'
77
+ )
78
+
79
+ # Add marker with correct coordinate order
80
+ folium.Marker(
81
+ location=[40.7128, -74.0060], # [latitude, longitude]
82
+ popup='New York City',
83
+ tooltip='Click for info',
84
+ icon=folium.Icon(color='red', icon='info-sign')
85
+ ).add_to(m)
86
+ ```
87
+
88
+ MULTIPLE MARKERS EXAMPLE:
89
+ ```python
90
+ import folium
91
+ import pandas as pd
92
+
93
+ # Sample data with lat/lon columns
94
+ stores = pd.DataFrame({
95
+ 'name': ['Store A', 'Store B', 'Store C'],
96
+ 'latitude': [40.7128, 34.0522, 41.8781], # lat column
97
+ 'longitude': [-74.0060, -118.2437, -87.6298] # lon column
98
+ })
99
+
100
+ # Calculate center from data
101
+ center_lat = stores['latitude'].median()
102
+ center_lon = stores['longitude'].median()
103
+
104
+ # Create map centered on data
105
+ m = folium.Map(
106
+ location=[center_lat, center_lon], # ALWAYS [lat, lon]
107
+ zoom_start=5
108
+ )
109
+
110
+ # Add markers - iterate with correct order
111
+ for idx, row in stores.iterrows():
112
+ folium.Marker(
113
+ location=[row['latitude'], row['longitude']], # [lat, lon]
114
+ popup=f"Store: {row['name']}",
115
+ tooltip=row['name']
116
+ ).add_to(m)
117
+ ```
118
+
119
+ COORDINATE VALIDATION FUNCTION (OPTIONAL):
120
+ ```python
121
+ def validate_coords(lat, lon, name=""):
122
+ \"\"\"Validate lat/lon are in correct ranges.\"\"\"
123
+ if not (-90 <= lat <= 90):
124
+ print(f"⚠️ Invalid latitude for {name}: {lat}")
125
+ return False
126
+ if not (-180 <= lon <= 180):
127
+ print(f"⚠️ Invalid longitude for {name}: {lon}")
128
+ return False
129
+ return True
130
+
131
+ # Use before adding markers:
132
+ if validate_coords(lat, lon, store_name):
133
+ folium.Marker(location=[lat, lon], ...).add_to(m)
134
+ ```
135
+
136
+ DATA MODE (when DataFrame is provided):
137
+ If a DataFrame is provided with geographic data, return it as-is or with minimal processing.
138
+ The system will automatically combine it with GeoJSON to create choropleth maps.
139
+ Ensure the DataFrame has columns that can join with GeoJSON properties.
140
+
141
+ ADVANCED FEATURES:
142
+ - For heatmaps: use folium.plugins.HeatMap
143
+ - For polylines: use folium.PolyLine (coordinates in [lat, lon] order!)
144
+ - For circles: folium.Circle(location=[lat, lon], radius=...)
145
+ - For custom tiles: ALWAYS include attribution parameter
146
+ Example: folium.TileLayer('Stamen Terrain', attr='Map tiles by Stamen Design').add_to(m)
147
+ - Use clear, informative popups and tooltips
148
+
149
+ COMMON MISTAKES TO AVOID:
150
+ ❌ Using [lon, lat] order instead of [lat, lon]
151
+ ❌ Forgetting to calculate map center from data
152
+ ❌ Using fixed zoom level that doesn't fit the data
153
+ ❌ Not validating coordinate ranges
154
+ ❌ Swapping latitude and longitude column references
155
+
156
+ FINAL CHECKLIST BEFORE RETURNING CODE:
157
+ 1. ✓ All folium.Map() calls use [latitude, longitude] order
158
+ 2. ✓ All folium.Marker() calls use [latitude, longitude] order
159
+ 3. ✓ All coordinate arrays/lists use [latitude, longitude] order
160
+ 4. ✓ Map center is calculated from actual marker positions
161
+ 5. ✓ Zoom level is appropriate for geographic spread
162
+ 6. ✓ No longitude values in the latitude position
163
+ 7. ✓ No latitude values in the longitude position
164
+
165
+ Remember: LATITUDE FIRST, LONGITUDE SECOND. Always [lat, lon], never [lon, lat]!
166
+ """
167
+
168
+
169
+ FOLIUM_DATA_PROMPT = """FOLIUM DATA MODE:
170
+ You are generating data for a choropleth map.
171
+
172
+ REQUIREMENTS:
173
+ 1. Return a pandas DataFrame with geographic data
174
+ 2. Include a column that matches GeoJSON property keys (e.g., 'state', 'country', 'region_id')
175
+ 3. Include numeric columns for visualization (e.g., 'population', 'value', 'score')
176
+ 4. Data should be clean and ready for visualization
177
+
178
+ EXAMPLE OUTPUT (as Python code that creates DataFrame):
179
+ ```python
180
+ import pandas as pd
181
+
182
+ data = pd.DataFrame({
183
+ 'state': ['California', 'Texas', 'Florida', 'New York'],
184
+ 'population': [39538223, 29145505, 21538187, 20201249],
185
+ 'gdp': [3.4, 2.1, 1.2, 1.9]
186
+ })
187
+ ```
188
+ """
189
+
190
+
191
+ @register_renderer(OutputMode.MAP, system_prompt=FOLIUM_SYSTEM_PROMPT)
192
+ class FoliumRenderer(BaseChart):
193
+ """Renderer for Folium maps with support for DataFrames and GeoJSON"""
194
+
195
+ @classmethod
196
+ def get_expected_content_type(cls) -> type:
197
+ """
198
+ This renderer can work with both string (code) and DataFrame (data).
199
+ We'll handle both in the render method.
200
+ """
201
+ return Union[str, pd.DataFrame] if GEOPANDAS_AVAILABLE else str
202
+
203
+ def _is_valid_latitude(self, value: Any) -> bool:
204
+ """Check if value is a valid latitude (-90 to 90)."""
205
+ return isinstance(value, (int, float)) and -90 <= value <= 90
206
+
207
+
208
+ def _is_valid_longitude(self, value: Any) -> bool:
209
+ """Check if value is a valid longitude (-180 to 180)."""
210
+ return isinstance(value, (int, float)) and -180 <= value <= 180
211
+
212
+
213
+ def _detect_coordinate_swap(self, lat: float, lon: float) -> bool:
214
+ """
215
+ Detect if coordinates are likely swapped using multiple heuristics.
216
+
217
+ Returns True if coordinates appear to be swapped.
218
+ """
219
+ # Basic validation - both must be numeric
220
+ if not (isinstance(lat, (int, float)) and isinstance(lon, (int, float))):
221
+ return False
222
+
223
+ # Check 1: Basic range check
224
+ lat_in_lat_range = -90 <= lat <= 90
225
+ lon_in_lon_range = -180 <= lon <= 180
226
+ lat_in_lon_range = -180 <= lat <= 180
227
+ lon_in_lat_range = -90 <= lon <= 90
228
+
229
+ # If current order is invalid but swapped would be valid
230
+ if not (lat_in_lat_range and lon_in_lon_range):
231
+ if lat_in_lon_range and lon_in_lat_range:
232
+ return True # Definitely swapped
233
+
234
+ # Check 2: Magnitude heuristic for common locations
235
+ # Most inhabited locations: lat magnitude < 70, lon can be larger
236
+ if abs(lat) > 90:
237
+ return True # Invalid latitude, must be swapped
238
+
239
+ # Check 3: Sign heuristic for Western Hemisphere (Americas, especially US)
240
+ # For US/Americas: latitude should be positive (20-50), longitude negative (-60 to -180)
241
+ if lat < 0 and lon > 0:
242
+ # Negative latitude, positive longitude = likely Southern Hemisphere or swapped
243
+ # Check if swapping would make sense for US/Americas
244
+ if -130 <= lat <= -60 and 20 <= lon <= 50:
245
+ print(f" 📍 Detected likely swap (US coordinates): [{lat}, {lon}] → [{lon}, {lat}]")
246
+ return True
247
+
248
+ # Check 4: Extreme latitude with moderate longitude
249
+ # If latitude is very high/low (near poles) but longitude is moderate, might be swapped
250
+ if abs(lat) > 75 and abs(lon) < 75:
251
+ # Check if swapping makes more sense
252
+ if abs(lon) > 10 and abs(lat) > abs(lon):
253
+ # Probably swapped (unless actually near poles)
254
+ if not (85 <= abs(lat) <= 90): # Not actually at poles
255
+ return True
256
+
257
+ # Check 5: Florida-specific heuristic
258
+ # Florida: lat 24.5-31 (positive), lon -80 to -87 (negative)
259
+ if -90 <= lat <= -75 and 20 <= lon <= 35:
260
+ print(f" 🌴 Detected Florida coordinates swap: [{lat}, {lon}] → [{lon}, {lat}]")
261
+ return True
262
+
263
+ return False
264
+
265
+
266
+ def _normalize_location(self, location: Any) -> Tuple[Any, bool]:
267
+ """
268
+ Ensure coordinates are in [lat, lon] order and within valid ranges.
269
+
270
+ Returns:
271
+ (normalized_location, was_swapped)
272
+ """
273
+ if not isinstance(location, (list, tuple)) or len(location) < 2:
274
+ return location, False
275
+
276
+ lat, lon = location[0], location[1]
277
+
278
+ # Skip if not numeric
279
+ if not (isinstance(lat, (int, float)) and isinstance(lon, (int, float))):
280
+ return location, False
281
+
282
+ # Check if coordinates appear to be swapped
283
+ if self._detect_coordinate_swap(lat, lon):
284
+ # Swap coordinates
285
+ fixed_location = [lon, lat, *location[2:]]
286
+ return fixed_location, True
287
+
288
+ # Check if coordinates are valid as-is
289
+ if self._is_valid_latitude(lat) and self._is_valid_longitude(lon):
290
+ return list(location), False
291
+
292
+ # Invalid coordinates but swapping doesn't help
293
+ print(f" ⚠️ Invalid coordinates (can't auto-fix): [{lat}, {lon}]")
294
+ return list(location), False
295
+
296
+
297
+ def _prepare_map_coordinates(self, map_obj: Any) -> None:
298
+ """
299
+ Normalize marker coordinates and recenter the map.
300
+
301
+ Improvements:
302
+ - Better coordinate validation
303
+ - More detailed logging
304
+ - Robust center calculation
305
+ """
306
+ coordinates: List[Tuple[float, float]] = []
307
+ swaps = 0
308
+ invalid = 0
309
+ total_markers = 0
310
+
311
+ print("\n📍 Validating map coordinates...")
312
+
313
+ for child in getattr(map_obj, '_children', {}).values():
314
+ location = getattr(child, 'location', None)
315
+ if location is None:
316
+ continue
317
+
318
+ total_markers += 1
319
+ original_location = location.copy() if isinstance(location, list) else list(location)
320
+
321
+ fixed_location, was_swapped = self._normalize_location(location)
322
+
323
+ if was_swapped:
324
+ setattr(child, 'location', fixed_location)
325
+ swaps += 1
326
+ print(f" ✓ Fixed: {original_location} → {fixed_location}")
327
+
328
+ # Collect valid coordinates for centering
329
+ if isinstance(fixed_location, (list, tuple)) and len(fixed_location) >= 2:
330
+ first, second = fixed_location[0], fixed_location[1]
331
+ if self._is_valid_latitude(first) and self._is_valid_longitude(second):
332
+ coordinates.append((first, second))
333
+ else:
334
+ invalid += 1
335
+ print(f" ⚠️ Invalid coordinates (skipping): {fixed_location}")
336
+
337
+ # Update map center based on valid coordinates
338
+ if coordinates:
339
+ lats = pd.Series([lat for lat, _ in coordinates])
340
+ lons = pd.Series([lon for _, lon in coordinates])
341
+
342
+ new_center = [float(lats.median()), float(lons.median())]
343
+ old_center = map_obj.location
344
+
345
+ map_obj.location = new_center
346
+
347
+ print(f"\n📊 Coordinate validation summary:")
348
+ print(f" Total markers: {total_markers}")
349
+ print(f" Valid coordinates: {len(coordinates)}")
350
+ print(f" Swapped and fixed: {swaps}")
351
+ print(f" Invalid (skipped): {invalid}")
352
+ print(f" Map center: {old_center} → {new_center}")
353
+
354
+ # Suggest appropriate zoom level based on coordinate spread
355
+ lat_range = float(lats.max() - lats.min())
356
+ lon_range = float(lons.max() - lons.min())
357
+ max_range = max(lat_range, lon_range)
358
+
359
+ if max_range < 0.1:
360
+ suggested_zoom = 13 # Very tight cluster
361
+ elif max_range < 1:
362
+ suggested_zoom = 10 # City level
363
+ elif max_range < 5:
364
+ suggested_zoom = 7 # Regional
365
+ elif max_range < 20:
366
+ suggested_zoom = 5 # Multi-state
367
+ else:
368
+ suggested_zoom = 3 # Continental
369
+
370
+ # Update zoom if it seems wrong
371
+ current_zoom = map_obj.options.get('zoom', map_obj.options.get('zoom_start', 10))
372
+ if abs(current_zoom - suggested_zoom) > 3:
373
+ print(f" 💡 Suggested zoom: {suggested_zoom} (current: {current_zoom})")
374
+
375
+ else:
376
+ print(f" ⚠️ No valid coordinates found among {total_markers} markers")
377
+
378
+ print() # Empty line for readability
379
+
380
+ def execute_code(
381
+ self,
382
+ code: str,
383
+ pandas_tool: Any = None,
384
+ execution_state: Optional[Dict[str, Any]] = None,
385
+ **kwargs,
386
+ ) -> Tuple[Any, Optional[str]]:
387
+ """Execute Folium map code and return map object."""
388
+ extra_namespace = None
389
+ if pandas_tool is None:
390
+ try:
391
+ import folium
392
+ extra_namespace = {'folium': folium}
393
+ except ImportError:
394
+ return None, "folium library not available"
395
+
396
+ context, error = super().execute_code(
397
+ code,
398
+ pandas_tool=pandas_tool,
399
+ execution_state=execution_state,
400
+ extra_namespace=extra_namespace,
401
+ **kwargs,
402
+ )
403
+
404
+ if error:
405
+ return None, error
406
+
407
+ if not context:
408
+ return None, "Execution context was empty"
409
+
410
+ # Debug: print all variables in context
411
+ # print(f"CONTEXT KEYS: {list(context.keys())}")
412
+
413
+ # Try to find map object
414
+ map_obj = None
415
+ for var_name in ['m', 'map', 'folium_map', 'my_map']:
416
+ if var_name in context:
417
+ obj = context[var_name]
418
+ print(f"Found variable '{var_name}': {type(obj)}")
419
+ # Check if it's a folium Map
420
+ if hasattr(obj, '_name') and hasattr(obj, 'location'):
421
+ map_obj = obj
422
+ break
423
+
424
+ # If still None, try to find any folium.Map object
425
+ if map_obj is None:
426
+ for var_name, obj in context.items():
427
+ if var_name.startswith('_'):
428
+ continue
429
+ # Check if it's a folium Map by class name
430
+ if obj.__class__.__name__ == 'Map' and 'folium' in obj.__class__.__module__:
431
+ print(f"Found folium Map in variable '{var_name}'")
432
+ map_obj = obj
433
+ break
434
+
435
+ # Handle DataFrame case (for data mode)
436
+ if map_obj is None:
437
+ for var_name in ['data', 'df']:
438
+ if var_name in context and isinstance(context[var_name], pd.DataFrame):
439
+ return context[var_name], None
440
+
441
+ if map_obj is None:
442
+ # Provide helpful error message
443
+ available_vars = [k for k in context.keys() if not k.startswith('_')]
444
+ return None, (
445
+ f"Code must define a folium Map variable (m, map, folium_map, or my_map). "
446
+ f"Available variables: {', '.join(available_vars)}"
447
+ )
448
+
449
+ return map_obj, None
450
+
451
+ def _create_choropleth_map(
452
+ self,
453
+ data: pd.DataFrame,
454
+ geojson_path: str,
455
+ key_on: str,
456
+ columns: Tuple[str, str],
457
+ **kwargs
458
+ ) -> Any:
459
+ """Create a choropleth map from DataFrame and GeoJSON."""
460
+ if not GEOPANDAS_AVAILABLE:
461
+ raise ImportError("geopandas is required for choropleth maps")
462
+
463
+ if isinstance(geojson_path, (str, Path)):
464
+ gdf = gpd.read_file(geojson_path)
465
+ else:
466
+ gdf = geojson_path
467
+
468
+ center = kwargs.get('center')
469
+ if center is None:
470
+ bounds = gdf.total_bounds
471
+ center = [(bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2]
472
+
473
+ m = folium.Map(
474
+ location=center,
475
+ zoom_start=kwargs.get('zoom_start', 6),
476
+ tiles=kwargs.get('tiles', 'OpenStreetMap')
477
+ )
478
+
479
+ folium.Choropleth(
480
+ geo_data=gdf,
481
+ name='choropleth',
482
+ data=data,
483
+ columns=columns,
484
+ key_on=key_on,
485
+ fill_color=kwargs.get('fill_color', 'YlOrRd'),
486
+ fill_opacity=kwargs.get('fill_opacity', 0.7),
487
+ line_opacity=kwargs.get('line_opacity', 0.2),
488
+ legend_name=kwargs.get('legend_name', columns[1]),
489
+ highlight=kwargs.get('highlight', True)
490
+ ).add_to(m)
491
+
492
+ if kwargs.get('layer_control', True):
493
+ folium.LayerControl().add_to(m)
494
+
495
+ if kwargs.get('add_tooltips', True):
496
+ self._add_choropleth_tooltips(m, gdf, data, columns, key_on)
497
+
498
+ return m
499
+
500
+ def _add_choropleth_tooltips(
501
+ self,
502
+ map_obj: Any,
503
+ gdf: gpd.GeoDataFrame,
504
+ data: pd.DataFrame,
505
+ columns: Tuple[str, str],
506
+ key_on: str
507
+ ):
508
+ """Add interactive tooltips to choropleth map."""
509
+ property_name = key_on.split('.')[-1]
510
+
511
+ gdf_with_data = gdf.merge(
512
+ data,
513
+ left_on=property_name,
514
+ right_on=columns[0],
515
+ how='left'
516
+ )
517
+
518
+ folium.GeoJson(
519
+ gdf_with_data,
520
+ style_function=lambda x: {
521
+ 'fillColor': 'transparent',
522
+ 'color': 'transparent',
523
+ 'weight': 0
524
+ },
525
+ tooltip=folium.GeoJsonTooltip(
526
+ fields=[property_name, columns[1]],
527
+ aliases=[property_name.capitalize(), columns[1].capitalize()],
528
+ localize=True
529
+ )
530
+ ).add_to(map_obj)
531
+
532
+ def _extract_head_resources(self, full_html: str) -> str:
533
+ """
534
+ Extracts scripts and styles from the <head> of the Folium HTML.
535
+ This allows us to pass them to BaseChart to include in the header.
536
+ """
537
+ head_match = re.search(r'<head[^>]*>(.*?)</head>', full_html, re.DOTALL)
538
+ if not head_match:
539
+ return ""
540
+
541
+ content = head_match[1]
542
+
543
+ # Capture full script/style/link tags to avoid malformed HTML fragments
544
+ resources: List[str] = []
545
+ for pattern in [
546
+ r'<script[^>]*>.*?</script>',
547
+ r'<style[^>]*>.*?</style>',
548
+ r'<link[^>]*?>',
549
+ ]:
550
+ resources.extend(re.findall(pattern, content, re.DOTALL))
551
+
552
+ return '\n'.join(resources)
553
+
554
+ def _render_chart_content(self, chart_obj: Any, **kwargs) -> str:
555
+ """
556
+ Render Folium map content (Body + Inline Scripts).
557
+ This implements the abstract method from BaseChart.
558
+
559
+ Note: we keep the original Folium-generated map ID to ensure all
560
+ associated styles and scripts continue to reference the same element.
561
+ This prevents broken layouts where the map container ends up with no
562
+ height because the IDs in the head and body fall out of sync.
563
+ """
564
+ # Render the map to a complete HTML string
565
+ full_html = chart_obj.get_root().render()
566
+
567
+ # Extract the body content (divs and inline scripts)
568
+ # We use the same logic as before, but now strictly for the body
569
+ explanation = kwargs.get('explanation')
570
+ explanation_block = self._build_explanation_section(explanation)
571
+ return f"{explanation_block}{self._extract_map_content(full_html)}"
572
+
573
+ def to_html(
574
+ self,
575
+ chart_obj: Any,
576
+ mode: str = 'partial',
577
+ **kwargs
578
+ ) -> str:
579
+ """
580
+ Convert Folium map to HTML using BaseChart's standard pipeline.
581
+ """
582
+ # 1. Generate the full Folium HTML internally to get resources
583
+ full_html = chart_obj.get_root().render()
584
+
585
+ # 2. Extract the CDN links and CSS from the head
586
+ extra_head = self._extract_head_resources(full_html)
587
+
588
+ # 3. Pass to parent to use standard template
589
+ # Note: parent calls self._render_chart_content internally
590
+ return super().to_html(
591
+ chart_obj,
592
+ mode=mode,
593
+ extra_head=extra_head, # Inject Folium JS/CSS here
594
+ icon='🗺️',
595
+ **kwargs
596
+ )
597
+
598
+ @staticmethod
599
+ def _extract_map_content(full_html: str, map_id: Optional[str] = None) -> str:
600
+ """
601
+ Extract map content (Divs + Script) from full Folium HTML.
602
+
603
+ We intentionally keep the original Folium-generated map ID unless a
604
+ custom ID is provided. This avoids mismatches between IDs referenced in
605
+ <head> resources (styles/scripts) and the body content that would
606
+ otherwise leave the map container with no height.
607
+ """
608
+ # 1. Extract Custom Styles
609
+ styles = []
610
+ for style_match in re.finditer(r'<style[^>]*>(.*?)</style>', full_html, re.DOTALL):
611
+ styles.append(style_match.group(0))
612
+
613
+ # 2. Find the map div
614
+ div_pattern = r'<div[^>]*id="(map_[^"]*)"[^>]*>.*?</div>'
615
+ div_match = re.search(div_pattern, full_html, re.DOTALL)
616
+
617
+ if div_match:
618
+ original_id = div_match[1]
619
+ map_id = map_id or original_id
620
+
621
+ # Obtenemos el HTML crudo del div
622
+ map_div = div_match[0]
623
+
624
+ # --- PASO 1: Actualizar el ID ---
625
+ map_div = map_div.replace(f'id="{original_id}"', f'id="{map_id}"')
626
+
627
+ # --- PASO 2: Inyectar Altura Fija (La solución al problema) ---
628
+ # Definimos la altura deseada
629
+ fixed_height_style = "height: 600px; min-height: 600px;"
630
+
631
+ # Intentamos reemplazar la altura porcentual que genera Folium (ej: height: 100.0%;)
632
+ # Usamos Regex para ser flexibles con espacios o decimales
633
+ map_div, num_subs = re.subn(
634
+ r'height:\s*100(\.0)?%;',
635
+ fixed_height_style,
636
+ map_div
637
+ )
638
+
639
+ # Si el regex no encontró nada (ej: Folium cambió formato), inyectamos el estilo a la fuerza
640
+ if num_subs == 0:
641
+ if 'style="' in map_div:
642
+ # Agregamos al principio del estilo existente con !important por si acaso
643
+ map_div = map_div.replace('style="', f'style="{fixed_height_style} ')
644
+ else:
645
+ # Si no hay estilo, creamos uno
646
+ map_div = map_div.replace('<div', f'<div style="{fixed_height_style}"')
647
+
648
+ # 3. Extract Inline Scripts
649
+ inline_scripts = []
650
+ for script_match in re.finditer(r'<script[^>]*>(.*?)</script>', full_html, re.DOTALL):
651
+ opening_tag = script_match.group(0)
652
+ script_content = script_match.group(1)
653
+
654
+ if 'src=' not in opening_tag and script_content.strip():
655
+ updated_script = script_content.replace(f'"{original_id}"', f'"{map_id}"')
656
+ updated_script = updated_script.replace(f"'{original_id}'", f"'{map_id}'")
657
+ inline_scripts.append(updated_script)
658
+ else:
659
+ map_id = map_id or f'folium-map-{uuid.uuid4().hex[:8]}'
660
+ # Fallback en caso de error de regex general
661
+ map_div = f'<div id="{map_id}" style="width: 100%; height: 600px;">Map Rendering Error</div>'
662
+ inline_scripts = []
663
+
664
+ # 4. Combine with proper newlines
665
+ parts = []
666
+
667
+ # Add styles with separation
668
+ if styles:
669
+ parts.extend(styles)
670
+ parts.append('') # Extra newline after styles
671
+
672
+ # Add map div
673
+ parts.append(map_div)
674
+ parts.append('') # Extra newline after div
675
+
676
+ # Add scripts with proper formatting
677
+ if inline_scripts:
678
+ parts.append('<script>')
679
+ parts.append('') # Newline after opening tag
680
+ parts.extend(inline_scripts)
681
+ parts.append('') # Newline before closing tag
682
+ parts.append('</script>')
683
+
684
+ # Join with double newlines for readability
685
+ return '\n\n'.join(parts)
686
+
687
+ @staticmethod
688
+ def _build_explanation_section(explanation: Optional[str]) -> str:
689
+ """Build a collapsible explanation section to show above the map."""
690
+ if not explanation:
691
+ return ""
692
+
693
+ escaped_explanation = html.escape(str(explanation))
694
+
695
+ return '''
696
+ <style>
697
+ .ap-map-explanation {margin-bottom: 16px;}
698
+ .ap-map-explanation details {border: 1px solid #e0e0e0; border-radius: 6px; overflow: hidden; background: #ffffff;}
699
+ .ap-map-explanation summary {background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); color: #fff; padding: 12px 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; font-weight: 600; user-select: none;}
700
+ .ap-map-explanation .ap-toggle-icon {transition: transform 0.3s ease;}
701
+ .ap-map-explanation details[open] .ap-toggle-icon {transform: rotate(90deg);}
702
+ .ap-map-explanation .ap-explanation-content {padding: 12px 16px; background: #f8fafc; color: #1f2937;}
703
+ .ap-map-explanation p {margin: 0; line-height: 1.6;}
704
+ </style>
705
+ <div class="ap-map-explanation">
706
+ <details>
707
+ <summary>
708
+ <span>📝 Explicación del mapa</span>
709
+ <span class="ap-toggle-icon">▶</span>
710
+ </summary>
711
+ <div class="ap-explanation-content">
712
+ <p>{escaped_explanation}</p>
713
+ </div>
714
+ </details>
715
+ </div>
716
+ '''.format(escaped_explanation=escaped_explanation)
717
+
718
+ @staticmethod
719
+ def _is_latitude(value: Any) -> bool:
720
+ return isinstance(value, (int, float)) and -90 <= value <= 90
721
+
722
+ @staticmethod
723
+ def _is_longitude(value: Any) -> bool:
724
+ return isinstance(value, (int, float)) and -180 <= value <= 180
725
+
726
+ def _normalize_location(self, location: Any) -> Tuple[Any, bool]:
727
+ """Ensure coordinates are in [lat, lon] order and within valid ranges."""
728
+ if not isinstance(location, (list, tuple)) or len(location) < 2:
729
+ return location, False
730
+
731
+ lat, lon = location[0], location[1]
732
+ if not isinstance(lat, (int, float)) or not isinstance(lon, (int, float)):
733
+ return location, False
734
+
735
+ lat_first_valid = self._is_latitude(lat) and self._is_longitude(lon)
736
+ lon_first_valid = self._is_latitude(lon) and self._is_longitude(lat)
737
+
738
+ # Detect clear reversals or polar misplacements
739
+ if not lat_first_valid and lon_first_valid:
740
+ return [lon, lat, *location[2:]], True
741
+
742
+ # Heuristic: if latitude magnitude is extreme while longitude is moderate, swap
743
+ if lat_first_valid and abs(lat) > 75 and abs(lon) < 75 and lon_first_valid:
744
+ return [lon, lat, *location[2:]], True
745
+
746
+ return list(location), False
747
+
748
+ def _prepare_map_coordinates(self, map_obj: Any) -> None:
749
+ """Normalize marker coordinates and recenter the map."""
750
+ coordinates: List[Tuple[float, float]] = []
751
+ swaps = 0
752
+
753
+ for child in getattr(map_obj, '_children', {}).values():
754
+ location = getattr(child, 'location', None)
755
+ fixed_location, swapped = self._normalize_location(location)
756
+ if swapped:
757
+ setattr(child, 'location', fixed_location)
758
+ swaps += 1
759
+ if isinstance(fixed_location, (list, tuple)) and len(fixed_location) >= 2:
760
+ first, second = fixed_location[0], fixed_location[1]
761
+ if self._is_latitude(first) and self._is_longitude(second):
762
+ coordinates.append((first, second))
763
+
764
+ if coordinates:
765
+ lats = pd.Series([lat for lat, _ in coordinates])
766
+ lons = pd.Series([lon for _, lon in coordinates])
767
+ map_obj.location = [float(lats.median()), float(lons.median())]
768
+ if swaps:
769
+ print(f"Corrected {swaps} marker coordinate pairs to [lat, lon] order.")
770
+
771
+ def to_json(self, chart_obj: Any) -> Optional[Dict]:
772
+ """Export map metadata as JSON."""
773
+ try:
774
+ return {
775
+ 'center': chart_obj.location,
776
+ 'zoom': chart_obj.options.get('zoom_start', chart_obj.options.get('zoom', 10)),
777
+ 'tiles': chart_obj.tiles if hasattr(chart_obj, 'tiles') else 'OpenStreetMap',
778
+ 'type': 'folium_map'
779
+ }
780
+ except Exception as e:
781
+ return {'error': str(e)}
782
+
783
+ async def render(
784
+ self,
785
+ response: Any,
786
+ theme: str = 'monokai',
787
+ environment: str = 'html',
788
+ include_code: bool = False,
789
+ html_mode: str = 'partial',
790
+ **kwargs
791
+ ) -> Tuple[Any, Optional[Any]]:
792
+ """
793
+ Render Folium map.
794
+
795
+ CRITICAL: Always returns (code, html) tuple
796
+ - First return (code): Python code string for response.output
797
+ - Second return (html): HTML content for response.response
798
+ """
799
+ explanation = getattr(response, 'explanation', None)
800
+
801
+ # 1. Extract Code - Try response.code first, fallback to content extraction
802
+ code = None
803
+ try:
804
+ code = getattr(response, 'code', None)
805
+ except Exception:
806
+ pass
807
+
808
+ # Fallback: extract from content if code is not available
809
+ if not code:
810
+ try:
811
+ content = self._get_content(response)
812
+ code = self._extract_code(content)
813
+ except Exception:
814
+ pass
815
+
816
+ # 2. Extract DataFrame - Try response.data first, then check content
817
+ dataframe = None
818
+ try:
819
+ dataframe = getattr(response, 'data', None)
820
+ if dataframe is not None and not isinstance(dataframe, pd.DataFrame):
821
+ dataframe = None
822
+ except Exception:
823
+ pass
824
+
825
+ # Fallback: check if content is a DataFrame
826
+ if dataframe is None:
827
+ try:
828
+ content = self._get_content(response)
829
+ if isinstance(content, pd.DataFrame):
830
+ dataframe = content
831
+ except Exception:
832
+ pass
833
+
834
+ output_format = kwargs.get('output_format', environment)
835
+ geojson_path = kwargs.get('geojson_path') or kwargs.get('geojson')
836
+
837
+ # --- DATA MODE (DataFrame + GeoJSON) ---
838
+ if GEOPANDAS_AVAILABLE and dataframe is not None and geojson_path:
839
+ try:
840
+ key_on = kwargs.get('key_on', 'feature.properties.name')
841
+ join_column = kwargs.get('join_column', dataframe.columns[0])
842
+ value_column = kwargs.get('value_column', dataframe.columns[1])
843
+
844
+ map_obj = self._create_choropleth_map(
845
+ data=dataframe,
846
+ geojson_path=geojson_path,
847
+ key_on=key_on,
848
+ columns=(join_column, value_column),
849
+ **kwargs
850
+ )
851
+
852
+ # Use to_html (which now uses super().to_html)
853
+ html_output = self.to_html(
854
+ map_obj,
855
+ mode=html_mode,
856
+ include_code=False,
857
+ title=kwargs.get('title', 'Choropleth Map'),
858
+ explanation=explanation,
859
+ **kwargs
860
+ )
861
+
862
+ # CRITICAL: Always return (code_string, html)
863
+ data_info = f"# Choropleth map with {len(dataframe)} regions"
864
+ return data_info, html_output
865
+
866
+ except Exception as e:
867
+ error_msg = f"Error creating choropleth: {str(e)}"
868
+ error_html = self._render_error(error_msg, code or "", theme)
869
+ # CRITICAL: Return code first, then error HTML
870
+ return code or f"# {error_msg}", error_html
871
+
872
+ # --- CODE MODE ---
873
+ if not code:
874
+ error_msg = "No map code found in response"
875
+ error_html = f"<div class='error'>{error_msg}</div>"
876
+ # CRITICAL: Return error message as code, error HTML as second value
877
+ return f"# {error_msg}", error_html
878
+
879
+ # Validate code completeness - check if it actually creates a map
880
+ if 'folium.Map' not in code and 'folium_map' not in code and 'm = ' not in code and 'map = ' not in code:
881
+ warning_msg = "Warning: Code appears incomplete - no map creation detected"
882
+ print(f"⚠️ {warning_msg}")
883
+ print(f"CODE PREVIEW: {code[:200]}...")
884
+ # Continue execution anyway - maybe the map is created differently
885
+
886
+ # Execute code
887
+ result_obj, error = self.execute_code(
888
+ code,
889
+ pandas_tool=kwargs.pop('pandas_tool', None),
890
+ execution_state=kwargs.pop('execution_state', None),
891
+ **kwargs,
892
+ )
893
+
894
+ if error:
895
+ error_html = self._render_error(error, code, theme)
896
+ # CRITICAL: Always return original code first, error HTML second
897
+ return code, error_html
898
+
899
+ # Handle if result is a DataFrame (data mode without GeoJSON)
900
+ if isinstance(result_obj, pd.DataFrame):
901
+ # Return code and DataFrame info
902
+ df_info = f"<div>DataFrame with {len(result_obj)} rows and {len(result_obj.columns)} columns</div>"
903
+ return code, df_info
904
+
905
+ # Result is a Folium map object
906
+ map_obj = result_obj
907
+
908
+ # Normalize coordinates and center based on available markers
909
+ self._prepare_map_coordinates(map_obj)
910
+
911
+ # Handle Jupyter/Notebook Environment
912
+ if output_format in {'jupyter', 'notebook', 'ipython', 'colab'}:
913
+ # For Jupyter, return code and map object
914
+ return code, map_obj
915
+
916
+ # Generate HTML for Web/Terminal
917
+ html_output = self.to_html(
918
+ map_obj,
919
+ mode=html_mode,
920
+ include_code=include_code,
921
+ code=code,
922
+ theme=theme,
923
+ title=kwargs.get('title', 'Folium Map'),
924
+ explanation=explanation,
925
+ **kwargs
926
+ )
927
+
928
+ # Return based on output format
929
+ if output_format == 'json':
930
+ return code, self.to_json(map_obj)
931
+
932
+ # Default: Always return (code_string, html_string)
933
+ return code, html_output