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.
- agentui/.prettierrc +15 -0
- agentui/QUICKSTART.md +272 -0
- agentui/README.md +59 -0
- agentui/env.example +16 -0
- agentui/jsconfig.json +14 -0
- agentui/package-lock.json +4242 -0
- agentui/package.json +34 -0
- agentui/scripts/postinstall/apply-patches.mjs +260 -0
- agentui/src/app.css +61 -0
- agentui/src/app.d.ts +13 -0
- agentui/src/app.html +12 -0
- agentui/src/components/LoadingSpinner.svelte +64 -0
- agentui/src/components/ThemeSwitcher.svelte +159 -0
- agentui/src/components/index.js +4 -0
- agentui/src/lib/api/bots.ts +60 -0
- agentui/src/lib/api/chat.ts +22 -0
- agentui/src/lib/api/http.ts +25 -0
- agentui/src/lib/components/BotCard.svelte +33 -0
- agentui/src/lib/components/ChatBubble.svelte +63 -0
- agentui/src/lib/components/Toast.svelte +21 -0
- agentui/src/lib/config.ts +20 -0
- agentui/src/lib/stores/auth.svelte.ts +73 -0
- agentui/src/lib/stores/theme.svelte.js +64 -0
- agentui/src/lib/stores/toast.svelte.ts +31 -0
- agentui/src/lib/utils/conversation.ts +39 -0
- agentui/src/routes/+layout.svelte +20 -0
- agentui/src/routes/+page.svelte +232 -0
- agentui/src/routes/login/+page.svelte +200 -0
- agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
- agentui/src/routes/talk/[agentId]/+page.ts +7 -0
- agentui/static/README.md +1 -0
- agentui/svelte.config.js +11 -0
- agentui/tailwind.config.ts +53 -0
- agentui/tsconfig.json +3 -0
- agentui/vite.config.ts +10 -0
- ai_parrot-0.17.2.dist-info/METADATA +472 -0
- ai_parrot-0.17.2.dist-info/RECORD +535 -0
- ai_parrot-0.17.2.dist-info/WHEEL +6 -0
- ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
- ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
- ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
- crew-builder/.prettierrc +15 -0
- crew-builder/QUICKSTART.md +259 -0
- crew-builder/README.md +113 -0
- crew-builder/env.example +17 -0
- crew-builder/jsconfig.json +14 -0
- crew-builder/package-lock.json +4182 -0
- crew-builder/package.json +37 -0
- crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
- crew-builder/src/app.css +62 -0
- crew-builder/src/app.d.ts +13 -0
- crew-builder/src/app.html +12 -0
- crew-builder/src/components/LoadingSpinner.svelte +64 -0
- crew-builder/src/components/ThemeSwitcher.svelte +149 -0
- crew-builder/src/components/index.js +9 -0
- crew-builder/src/lib/api/bots.ts +60 -0
- crew-builder/src/lib/api/chat.ts +80 -0
- crew-builder/src/lib/api/client.ts +56 -0
- crew-builder/src/lib/api/crew/crew.ts +136 -0
- crew-builder/src/lib/api/index.ts +5 -0
- crew-builder/src/lib/api/o365/auth.ts +65 -0
- crew-builder/src/lib/auth/auth.ts +54 -0
- crew-builder/src/lib/components/AgentNode.svelte +43 -0
- crew-builder/src/lib/components/BotCard.svelte +33 -0
- crew-builder/src/lib/components/ChatBubble.svelte +67 -0
- crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
- crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
- crew-builder/src/lib/components/JsonViewer.svelte +24 -0
- crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
- crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
- crew-builder/src/lib/components/Toast.svelte +67 -0
- crew-builder/src/lib/components/Toolbar.svelte +157 -0
- crew-builder/src/lib/components/index.ts +10 -0
- crew-builder/src/lib/config.ts +8 -0
- crew-builder/src/lib/stores/auth.svelte.ts +228 -0
- crew-builder/src/lib/stores/crewStore.ts +369 -0
- crew-builder/src/lib/stores/theme.svelte.js +145 -0
- crew-builder/src/lib/stores/toast.svelte.ts +69 -0
- crew-builder/src/lib/utils/conversation.ts +39 -0
- crew-builder/src/lib/utils/markdown.ts +122 -0
- crew-builder/src/lib/utils/talkHistory.ts +47 -0
- crew-builder/src/routes/+layout.svelte +20 -0
- crew-builder/src/routes/+page.svelte +539 -0
- crew-builder/src/routes/agents/+page.svelte +247 -0
- crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
- crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
- crew-builder/src/routes/builder/+page.svelte +204 -0
- crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
- crew-builder/src/routes/crew/ask/+page.ts +1 -0
- crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
- crew-builder/src/routes/login/+page.svelte +197 -0
- crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
- crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
- crew-builder/static/README.md +1 -0
- crew-builder/svelte.config.js +11 -0
- crew-builder/tailwind.config.ts +53 -0
- crew-builder/tsconfig.json +3 -0
- crew-builder/vite.config.ts +10 -0
- mcp_servers/calculator_server.py +309 -0
- parrot/__init__.py +27 -0
- parrot/__pycache__/__init__.cpython-310.pyc +0 -0
- parrot/__pycache__/version.cpython-310.pyc +0 -0
- parrot/_version.py +34 -0
- parrot/a2a/__init__.py +48 -0
- parrot/a2a/client.py +658 -0
- parrot/a2a/discovery.py +89 -0
- parrot/a2a/mixin.py +257 -0
- parrot/a2a/models.py +376 -0
- parrot/a2a/server.py +770 -0
- parrot/agents/__init__.py +29 -0
- parrot/bots/__init__.py +12 -0
- parrot/bots/a2a_agent.py +19 -0
- parrot/bots/abstract.py +3139 -0
- parrot/bots/agent.py +1129 -0
- parrot/bots/basic.py +9 -0
- parrot/bots/chatbot.py +669 -0
- parrot/bots/data.py +1618 -0
- parrot/bots/database/__init__.py +5 -0
- parrot/bots/database/abstract.py +3071 -0
- parrot/bots/database/cache.py +286 -0
- parrot/bots/database/models.py +468 -0
- parrot/bots/database/prompts.py +154 -0
- parrot/bots/database/retries.py +98 -0
- parrot/bots/database/router.py +269 -0
- parrot/bots/database/sql.py +41 -0
- parrot/bots/db/__init__.py +6 -0
- parrot/bots/db/abstract.py +556 -0
- parrot/bots/db/bigquery.py +602 -0
- parrot/bots/db/cache.py +85 -0
- parrot/bots/db/documentdb.py +668 -0
- parrot/bots/db/elastic.py +1014 -0
- parrot/bots/db/influx.py +898 -0
- parrot/bots/db/mock.py +96 -0
- parrot/bots/db/multi.py +783 -0
- parrot/bots/db/prompts.py +185 -0
- parrot/bots/db/sql.py +1255 -0
- parrot/bots/db/tools.py +212 -0
- parrot/bots/document.py +680 -0
- parrot/bots/hrbot.py +15 -0
- parrot/bots/kb.py +170 -0
- parrot/bots/mcp.py +36 -0
- parrot/bots/orchestration/README.md +463 -0
- parrot/bots/orchestration/__init__.py +1 -0
- parrot/bots/orchestration/agent.py +155 -0
- parrot/bots/orchestration/crew.py +3330 -0
- parrot/bots/orchestration/fsm.py +1179 -0
- parrot/bots/orchestration/hr.py +434 -0
- parrot/bots/orchestration/storage/__init__.py +4 -0
- parrot/bots/orchestration/storage/memory.py +100 -0
- parrot/bots/orchestration/storage/mixin.py +119 -0
- parrot/bots/orchestration/verify.py +202 -0
- parrot/bots/product.py +204 -0
- parrot/bots/prompts/__init__.py +96 -0
- parrot/bots/prompts/agents.py +155 -0
- parrot/bots/prompts/data.py +216 -0
- parrot/bots/prompts/output_generation.py +8 -0
- parrot/bots/scraper/__init__.py +3 -0
- parrot/bots/scraper/models.py +122 -0
- parrot/bots/scraper/scraper.py +1173 -0
- parrot/bots/scraper/templates.py +115 -0
- parrot/bots/stores/__init__.py +5 -0
- parrot/bots/stores/local.py +172 -0
- parrot/bots/webdev.py +81 -0
- parrot/cli.py +17 -0
- parrot/clients/__init__.py +16 -0
- parrot/clients/base.py +1491 -0
- parrot/clients/claude.py +1191 -0
- parrot/clients/factory.py +129 -0
- parrot/clients/google.py +4567 -0
- parrot/clients/gpt.py +1975 -0
- parrot/clients/grok.py +432 -0
- parrot/clients/groq.py +986 -0
- parrot/clients/hf.py +582 -0
- parrot/clients/models.py +18 -0
- parrot/conf.py +395 -0
- parrot/embeddings/__init__.py +9 -0
- parrot/embeddings/base.py +157 -0
- parrot/embeddings/google.py +98 -0
- parrot/embeddings/huggingface.py +74 -0
- parrot/embeddings/openai.py +84 -0
- parrot/embeddings/processor.py +88 -0
- parrot/exceptions.c +13868 -0
- parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/exceptions.pxd +22 -0
- parrot/exceptions.pxi +15 -0
- parrot/exceptions.pyx +44 -0
- parrot/generators/__init__.py +29 -0
- parrot/generators/base.py +200 -0
- parrot/generators/html.py +293 -0
- parrot/generators/react.py +205 -0
- parrot/generators/streamlit.py +203 -0
- parrot/generators/template.py +105 -0
- parrot/handlers/__init__.py +4 -0
- parrot/handlers/agent.py +861 -0
- parrot/handlers/agents/__init__.py +1 -0
- parrot/handlers/agents/abstract.py +900 -0
- parrot/handlers/bots.py +338 -0
- parrot/handlers/chat.py +915 -0
- parrot/handlers/creation.sql +192 -0
- parrot/handlers/crew/ARCHITECTURE.md +362 -0
- parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
- parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
- parrot/handlers/crew/__init__.py +0 -0
- parrot/handlers/crew/handler.py +801 -0
- parrot/handlers/crew/models.py +229 -0
- parrot/handlers/crew/redis_persistence.py +523 -0
- parrot/handlers/jobs/__init__.py +10 -0
- parrot/handlers/jobs/job.py +384 -0
- parrot/handlers/jobs/mixin.py +627 -0
- parrot/handlers/jobs/models.py +115 -0
- parrot/handlers/jobs/worker.py +31 -0
- parrot/handlers/models.py +596 -0
- parrot/handlers/o365_auth.py +105 -0
- parrot/handlers/stream.py +337 -0
- parrot/interfaces/__init__.py +6 -0
- parrot/interfaces/aws.py +143 -0
- parrot/interfaces/credentials.py +113 -0
- parrot/interfaces/database.py +27 -0
- parrot/interfaces/google.py +1123 -0
- parrot/interfaces/hierarchy.py +1227 -0
- parrot/interfaces/http.py +651 -0
- parrot/interfaces/images/__init__.py +0 -0
- parrot/interfaces/images/plugins/__init__.py +24 -0
- parrot/interfaces/images/plugins/abstract.py +58 -0
- parrot/interfaces/images/plugins/analisys.py +148 -0
- parrot/interfaces/images/plugins/classify.py +150 -0
- parrot/interfaces/images/plugins/classifybase.py +182 -0
- parrot/interfaces/images/plugins/detect.py +150 -0
- parrot/interfaces/images/plugins/exif.py +1103 -0
- parrot/interfaces/images/plugins/hash.py +52 -0
- parrot/interfaces/images/plugins/vision.py +104 -0
- parrot/interfaces/images/plugins/yolo.py +66 -0
- parrot/interfaces/images/plugins/zerodetect.py +197 -0
- parrot/interfaces/o365.py +978 -0
- parrot/interfaces/onedrive.py +822 -0
- parrot/interfaces/sharepoint.py +1435 -0
- parrot/interfaces/soap.py +257 -0
- parrot/loaders/__init__.py +8 -0
- parrot/loaders/abstract.py +1131 -0
- parrot/loaders/audio.py +199 -0
- parrot/loaders/basepdf.py +53 -0
- parrot/loaders/basevideo.py +1568 -0
- parrot/loaders/csv.py +409 -0
- parrot/loaders/docx.py +116 -0
- parrot/loaders/epubloader.py +316 -0
- parrot/loaders/excel.py +199 -0
- parrot/loaders/factory.py +55 -0
- parrot/loaders/files/__init__.py +0 -0
- parrot/loaders/files/abstract.py +39 -0
- parrot/loaders/files/html.py +26 -0
- parrot/loaders/files/text.py +63 -0
- parrot/loaders/html.py +152 -0
- parrot/loaders/markdown.py +442 -0
- parrot/loaders/pdf.py +373 -0
- parrot/loaders/pdfmark.py +320 -0
- parrot/loaders/pdftables.py +506 -0
- parrot/loaders/ppt.py +476 -0
- parrot/loaders/qa.py +63 -0
- parrot/loaders/splitters/__init__.py +10 -0
- parrot/loaders/splitters/base.py +138 -0
- parrot/loaders/splitters/md.py +228 -0
- parrot/loaders/splitters/token.py +143 -0
- parrot/loaders/txt.py +26 -0
- parrot/loaders/video.py +89 -0
- parrot/loaders/videolocal.py +218 -0
- parrot/loaders/videounderstanding.py +377 -0
- parrot/loaders/vimeo.py +167 -0
- parrot/loaders/web.py +599 -0
- parrot/loaders/youtube.py +504 -0
- parrot/manager/__init__.py +5 -0
- parrot/manager/manager.py +1030 -0
- parrot/mcp/__init__.py +28 -0
- parrot/mcp/adapter.py +105 -0
- parrot/mcp/cli.py +174 -0
- parrot/mcp/client.py +119 -0
- parrot/mcp/config.py +75 -0
- parrot/mcp/integration.py +842 -0
- parrot/mcp/oauth.py +933 -0
- parrot/mcp/server.py +225 -0
- parrot/mcp/transports/__init__.py +3 -0
- parrot/mcp/transports/base.py +279 -0
- parrot/mcp/transports/grpc_session.py +163 -0
- parrot/mcp/transports/http.py +312 -0
- parrot/mcp/transports/mcp.proto +108 -0
- parrot/mcp/transports/quic.py +1082 -0
- parrot/mcp/transports/sse.py +330 -0
- parrot/mcp/transports/stdio.py +309 -0
- parrot/mcp/transports/unix.py +395 -0
- parrot/mcp/transports/websocket.py +547 -0
- parrot/memory/__init__.py +16 -0
- parrot/memory/abstract.py +209 -0
- parrot/memory/agent.py +32 -0
- parrot/memory/cache.py +175 -0
- parrot/memory/core.py +555 -0
- parrot/memory/file.py +153 -0
- parrot/memory/mem.py +131 -0
- parrot/memory/redis.py +613 -0
- parrot/models/__init__.py +46 -0
- parrot/models/basic.py +118 -0
- parrot/models/compliance.py +208 -0
- parrot/models/crew.py +395 -0
- parrot/models/detections.py +654 -0
- parrot/models/generation.py +85 -0
- parrot/models/google.py +223 -0
- parrot/models/groq.py +23 -0
- parrot/models/openai.py +30 -0
- parrot/models/outputs.py +285 -0
- parrot/models/responses.py +938 -0
- parrot/notifications/__init__.py +743 -0
- parrot/openapi/__init__.py +3 -0
- parrot/openapi/components.yaml +641 -0
- parrot/openapi/config.py +322 -0
- parrot/outputs/__init__.py +32 -0
- parrot/outputs/formats/__init__.py +108 -0
- parrot/outputs/formats/altair.py +359 -0
- parrot/outputs/formats/application.py +122 -0
- parrot/outputs/formats/base.py +351 -0
- parrot/outputs/formats/bokeh.py +356 -0
- parrot/outputs/formats/card.py +424 -0
- parrot/outputs/formats/chart.py +436 -0
- parrot/outputs/formats/d3.py +255 -0
- parrot/outputs/formats/echarts.py +310 -0
- parrot/outputs/formats/generators/__init__.py +0 -0
- parrot/outputs/formats/generators/abstract.py +61 -0
- parrot/outputs/formats/generators/panel.py +145 -0
- parrot/outputs/formats/generators/streamlit.py +86 -0
- parrot/outputs/formats/generators/terminal.py +63 -0
- parrot/outputs/formats/holoviews.py +310 -0
- parrot/outputs/formats/html.py +147 -0
- parrot/outputs/formats/jinja2.py +46 -0
- parrot/outputs/formats/json.py +87 -0
- parrot/outputs/formats/map.py +933 -0
- parrot/outputs/formats/markdown.py +172 -0
- parrot/outputs/formats/matplotlib.py +237 -0
- parrot/outputs/formats/mixins/__init__.py +0 -0
- parrot/outputs/formats/mixins/emaps.py +855 -0
- parrot/outputs/formats/plotly.py +341 -0
- parrot/outputs/formats/seaborn.py +310 -0
- parrot/outputs/formats/table.py +397 -0
- parrot/outputs/formats/template_report.py +138 -0
- parrot/outputs/formats/yaml.py +125 -0
- parrot/outputs/formatter.py +152 -0
- parrot/outputs/templates/__init__.py +95 -0
- parrot/pipelines/__init__.py +0 -0
- parrot/pipelines/abstract.py +210 -0
- parrot/pipelines/detector.py +124 -0
- parrot/pipelines/models.py +90 -0
- parrot/pipelines/planogram.py +3002 -0
- parrot/pipelines/table.sql +97 -0
- parrot/plugins/__init__.py +106 -0
- parrot/plugins/importer.py +80 -0
- parrot/py.typed +0 -0
- parrot/registry/__init__.py +18 -0
- parrot/registry/registry.py +594 -0
- parrot/scheduler/__init__.py +1189 -0
- parrot/scheduler/models.py +60 -0
- parrot/security/__init__.py +16 -0
- parrot/security/prompt_injection.py +268 -0
- parrot/security/security_events.sql +25 -0
- parrot/services/__init__.py +1 -0
- parrot/services/mcp/__init__.py +8 -0
- parrot/services/mcp/config.py +13 -0
- parrot/services/mcp/server.py +295 -0
- parrot/services/o365_remote_auth.py +235 -0
- parrot/stores/__init__.py +7 -0
- parrot/stores/abstract.py +352 -0
- parrot/stores/arango.py +1090 -0
- parrot/stores/bigquery.py +1377 -0
- parrot/stores/cache.py +106 -0
- parrot/stores/empty.py +10 -0
- parrot/stores/faiss_store.py +1157 -0
- parrot/stores/kb/__init__.py +9 -0
- parrot/stores/kb/abstract.py +68 -0
- parrot/stores/kb/cache.py +165 -0
- parrot/stores/kb/doc.py +325 -0
- parrot/stores/kb/hierarchy.py +346 -0
- parrot/stores/kb/local.py +457 -0
- parrot/stores/kb/prompt.py +28 -0
- parrot/stores/kb/redis.py +659 -0
- parrot/stores/kb/store.py +115 -0
- parrot/stores/kb/user.py +374 -0
- parrot/stores/models.py +59 -0
- parrot/stores/pgvector.py +3 -0
- parrot/stores/postgres.py +2853 -0
- parrot/stores/utils/__init__.py +0 -0
- parrot/stores/utils/chunking.py +197 -0
- parrot/telemetry/__init__.py +3 -0
- parrot/telemetry/mixin.py +111 -0
- parrot/template/__init__.py +3 -0
- parrot/template/engine.py +259 -0
- parrot/tools/__init__.py +23 -0
- parrot/tools/abstract.py +644 -0
- parrot/tools/agent.py +363 -0
- parrot/tools/arangodbsearch.py +537 -0
- parrot/tools/arxiv_tool.py +188 -0
- parrot/tools/calculator/__init__.py +3 -0
- parrot/tools/calculator/operations/__init__.py +38 -0
- parrot/tools/calculator/operations/calculus.py +80 -0
- parrot/tools/calculator/operations/statistics.py +76 -0
- parrot/tools/calculator/tool.py +150 -0
- parrot/tools/cloudwatch.py +988 -0
- parrot/tools/codeinterpreter/__init__.py +127 -0
- parrot/tools/codeinterpreter/executor.py +371 -0
- parrot/tools/codeinterpreter/internals.py +473 -0
- parrot/tools/codeinterpreter/models.py +643 -0
- parrot/tools/codeinterpreter/prompts.py +224 -0
- parrot/tools/codeinterpreter/tool.py +664 -0
- parrot/tools/company_info/__init__.py +6 -0
- parrot/tools/company_info/tool.py +1138 -0
- parrot/tools/correlationanalysis.py +437 -0
- parrot/tools/database/abstract.py +286 -0
- parrot/tools/database/bq.py +115 -0
- parrot/tools/database/cache.py +284 -0
- parrot/tools/database/models.py +95 -0
- parrot/tools/database/pg.py +343 -0
- parrot/tools/databasequery.py +1159 -0
- parrot/tools/db.py +1800 -0
- parrot/tools/ddgo.py +370 -0
- parrot/tools/decorators.py +271 -0
- parrot/tools/dftohtml.py +282 -0
- parrot/tools/document.py +549 -0
- parrot/tools/ecs.py +819 -0
- parrot/tools/edareport.py +368 -0
- parrot/tools/elasticsearch.py +1049 -0
- parrot/tools/employees.py +462 -0
- parrot/tools/epson/__init__.py +96 -0
- parrot/tools/excel.py +683 -0
- parrot/tools/file/__init__.py +13 -0
- parrot/tools/file/abstract.py +76 -0
- parrot/tools/file/gcs.py +378 -0
- parrot/tools/file/local.py +284 -0
- parrot/tools/file/s3.py +511 -0
- parrot/tools/file/tmp.py +309 -0
- parrot/tools/file/tool.py +501 -0
- parrot/tools/file_reader.py +129 -0
- parrot/tools/flowtask/__init__.py +19 -0
- parrot/tools/flowtask/tool.py +761 -0
- parrot/tools/gittoolkit.py +508 -0
- parrot/tools/google/__init__.py +18 -0
- parrot/tools/google/base.py +169 -0
- parrot/tools/google/tools.py +1251 -0
- parrot/tools/googlelocation.py +5 -0
- parrot/tools/googleroutes.py +5 -0
- parrot/tools/googlesearch.py +5 -0
- parrot/tools/googlesitesearch.py +5 -0
- parrot/tools/googlevoice.py +2 -0
- parrot/tools/gvoice.py +695 -0
- parrot/tools/ibisworld/README.md +225 -0
- parrot/tools/ibisworld/__init__.py +11 -0
- parrot/tools/ibisworld/tool.py +366 -0
- parrot/tools/jiratoolkit.py +1718 -0
- parrot/tools/manager.py +1098 -0
- parrot/tools/math.py +152 -0
- parrot/tools/metadata.py +476 -0
- parrot/tools/msteams.py +1621 -0
- parrot/tools/msword.py +635 -0
- parrot/tools/multidb.py +580 -0
- parrot/tools/multistoresearch.py +369 -0
- parrot/tools/networkninja.py +167 -0
- parrot/tools/nextstop/__init__.py +4 -0
- parrot/tools/nextstop/base.py +286 -0
- parrot/tools/nextstop/employee.py +733 -0
- parrot/tools/nextstop/store.py +462 -0
- parrot/tools/notification.py +435 -0
- parrot/tools/o365/__init__.py +42 -0
- parrot/tools/o365/base.py +295 -0
- parrot/tools/o365/bundle.py +522 -0
- parrot/tools/o365/events.py +554 -0
- parrot/tools/o365/mail.py +992 -0
- parrot/tools/o365/onedrive.py +497 -0
- parrot/tools/o365/sharepoint.py +641 -0
- parrot/tools/openapi_toolkit.py +904 -0
- parrot/tools/openweather.py +527 -0
- parrot/tools/pdfprint.py +1001 -0
- parrot/tools/powerbi.py +518 -0
- parrot/tools/powerpoint.py +1113 -0
- parrot/tools/pricestool.py +146 -0
- parrot/tools/products/__init__.py +246 -0
- parrot/tools/prophet_tool.py +171 -0
- parrot/tools/pythonpandas.py +630 -0
- parrot/tools/pythonrepl.py +910 -0
- parrot/tools/qsource.py +436 -0
- parrot/tools/querytoolkit.py +395 -0
- parrot/tools/quickeda.py +827 -0
- parrot/tools/resttool.py +553 -0
- parrot/tools/retail/__init__.py +0 -0
- parrot/tools/retail/bby.py +528 -0
- parrot/tools/sandboxtool.py +703 -0
- parrot/tools/sassie/__init__.py +352 -0
- parrot/tools/scraping/__init__.py +7 -0
- parrot/tools/scraping/docs/select.md +466 -0
- parrot/tools/scraping/documentation.md +1278 -0
- parrot/tools/scraping/driver.py +436 -0
- parrot/tools/scraping/models.py +576 -0
- parrot/tools/scraping/options.py +85 -0
- parrot/tools/scraping/orchestrator.py +517 -0
- parrot/tools/scraping/readme.md +740 -0
- parrot/tools/scraping/tool.py +3115 -0
- parrot/tools/seasonaldetection.py +642 -0
- parrot/tools/shell_tool/__init__.py +5 -0
- parrot/tools/shell_tool/actions.py +408 -0
- parrot/tools/shell_tool/engine.py +155 -0
- parrot/tools/shell_tool/models.py +322 -0
- parrot/tools/shell_tool/tool.py +442 -0
- parrot/tools/site_search.py +214 -0
- parrot/tools/textfile.py +418 -0
- parrot/tools/think.py +378 -0
- parrot/tools/toolkit.py +298 -0
- parrot/tools/webapp_tool.py +187 -0
- parrot/tools/whatif.py +1279 -0
- parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
- parrot/tools/workday/__init__.py +6 -0
- parrot/tools/workday/models.py +1389 -0
- parrot/tools/workday/tool.py +1293 -0
- parrot/tools/yfinance_tool.py +306 -0
- parrot/tools/zipcode.py +217 -0
- parrot/utils/__init__.py +2 -0
- parrot/utils/helpers.py +73 -0
- parrot/utils/parsers/__init__.py +5 -0
- parrot/utils/parsers/toml.c +12078 -0
- parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/parsers/toml.pyx +21 -0
- parrot/utils/toml.py +11 -0
- parrot/utils/types.cpp +20936 -0
- parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/types.pyx +213 -0
- parrot/utils/uv.py +11 -0
- parrot/version.py +10 -0
- parrot/yaml-rs/Cargo.lock +350 -0
- parrot/yaml-rs/Cargo.toml +19 -0
- parrot/yaml-rs/pyproject.toml +19 -0
- parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
- parrot/yaml-rs/src/lib.rs +222 -0
- requirements/docker-compose.yml +24 -0
- requirements/requirements-dev.txt +21 -0
parrot/tools/pdfprint.py
ADDED
|
@@ -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>"
|