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/gvoice.py
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Google Text-to-Speech Tool migrated to use AbstractTool framework with async support.
|
|
3
|
+
"""
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, Optional, Literal
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from xml.sax.saxutils import escape
|
|
11
|
+
import traceback
|
|
12
|
+
import aiofiles
|
|
13
|
+
import markdown
|
|
14
|
+
import bs4
|
|
15
|
+
from google.cloud import texttospeech_v1 as texttospeech
|
|
16
|
+
from google.oauth2 import service_account
|
|
17
|
+
from pydantic import BaseModel, Field, field_validator
|
|
18
|
+
from .abstract import AbstractTool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Markdown cleaning utilities
|
|
22
|
+
MD_REPLACEMENTS = [
|
|
23
|
+
# inline code: `print("hi")` → print("hi")
|
|
24
|
+
(r"`([^`]*)`", r"\1"),
|
|
25
|
+
# bold / italic: **text** or *text* or _text_ → text
|
|
26
|
+
(r"\*\*([^*]+)\*\*", r"\1"),
|
|
27
|
+
(r"[_*]([^_*]+)[_*]", r"\1"),
|
|
28
|
+
# strikethrough: ~~text~~
|
|
29
|
+
(r"~~([^~]+)~~", r"\1"),
|
|
30
|
+
# links: [label](url) → label
|
|
31
|
+
(r"\[([^\]]+)\]\([^)]+\)", r"\1"),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def strip_markdown(text: str) -> str:
|
|
36
|
+
"""Remove the most common inline Markdown markers."""
|
|
37
|
+
for pattern, repl in MD_REPLACEMENTS:
|
|
38
|
+
text = re.sub(pattern, repl, text)
|
|
39
|
+
return text
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def markdown_to_plain(md: str) -> str:
|
|
43
|
+
"""Convert Markdown to plain text via HTML parsing."""
|
|
44
|
+
html = markdown.markdown(md, extensions=["extra", "smarty"])
|
|
45
|
+
return ''.join(bs4.BeautifulSoup(html, "html.parser").stripped_strings)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GoogleTTSArgs(BaseModel):
|
|
49
|
+
"""Arguments schema for GoogleTTSTool."""
|
|
50
|
+
|
|
51
|
+
text: str = Field(
|
|
52
|
+
...,
|
|
53
|
+
description="The text content (plaintext or Markdown) to convert to speech"
|
|
54
|
+
)
|
|
55
|
+
voice_model: Optional[str] = Field(
|
|
56
|
+
None,
|
|
57
|
+
description="Specific Google voice model name (e.g., 'en-US-Neural2-F'). If None, selects based on language and gender"
|
|
58
|
+
)
|
|
59
|
+
voice_gender: Literal["MALE", "FEMALE"] = Field(
|
|
60
|
+
"FEMALE",
|
|
61
|
+
description="Voice gender preference when voice_model is not specified"
|
|
62
|
+
)
|
|
63
|
+
language_code: str = Field(
|
|
64
|
+
"en-US",
|
|
65
|
+
description="BCP-47 language code (e.g., 'en-US', 'es-ES', 'fr-FR')"
|
|
66
|
+
)
|
|
67
|
+
output_format: Literal["OGG_OPUS", "MP3", "LINEAR16", "MULAW", "ALAW", "PCM"] = Field(
|
|
68
|
+
"OGG_OPUS",
|
|
69
|
+
description="Audio output format"
|
|
70
|
+
)
|
|
71
|
+
file_prefix: str = Field(
|
|
72
|
+
"podcast",
|
|
73
|
+
description="Prefix for the output filename (timestamp and extension added automatically)"
|
|
74
|
+
)
|
|
75
|
+
speaking_rate: float = Field(
|
|
76
|
+
1.0,
|
|
77
|
+
description="Speaking rate (0.25 to 4.0, where 1.0 is normal speed)",
|
|
78
|
+
ge=0.25,
|
|
79
|
+
le=4.0
|
|
80
|
+
)
|
|
81
|
+
pitch: float = Field(
|
|
82
|
+
0.0,
|
|
83
|
+
description="Voice pitch (-20.0 to 20.0 semitones, where 0.0 is normal)",
|
|
84
|
+
ge=-20.0,
|
|
85
|
+
le=20.0
|
|
86
|
+
)
|
|
87
|
+
use_ssml: bool = Field(
|
|
88
|
+
True,
|
|
89
|
+
description="Whether to convert Markdown/text to SSML for better speech synthesis"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@field_validator('text')
|
|
93
|
+
@classmethod
|
|
94
|
+
def validate_text(cls, v):
|
|
95
|
+
if not v or not v.strip():
|
|
96
|
+
raise ValueError("Text content cannot be empty")
|
|
97
|
+
return v
|
|
98
|
+
|
|
99
|
+
@field_validator('language_code')
|
|
100
|
+
@classmethod
|
|
101
|
+
def validate_language_code(cls, v):
|
|
102
|
+
# Basic validation for BCP-47 format
|
|
103
|
+
if not re.match(r'^[a-z]{2,3}(-[A-Z]{2})?$', v):
|
|
104
|
+
raise ValueError("Language code must be in BCP-47 format (e.g., 'en-US', 'es-ES')")
|
|
105
|
+
return v
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class GoogleVoiceTool(AbstractTool):
|
|
109
|
+
"""
|
|
110
|
+
Tool for generating speech audio from text using Google Cloud Text-to-Speech.
|
|
111
|
+
|
|
112
|
+
This tool converts text content (including Markdown) into high-quality speech audio
|
|
113
|
+
using Google's neural voice models. It supports multiple languages, voice customization,
|
|
114
|
+
and various audio output formats.
|
|
115
|
+
|
|
116
|
+
Features:
|
|
117
|
+
- Automatic Markdown to SSML conversion for natural speech
|
|
118
|
+
- Multiple voice models and languages
|
|
119
|
+
- Configurable speech parameters (rate, pitch)
|
|
120
|
+
- Various audio output formats (OGG, MP3, WAV, etc.)
|
|
121
|
+
- Async processing for better performance
|
|
122
|
+
- Comprehensive error handling and logging
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
name = "google_tts_service"
|
|
126
|
+
description = (
|
|
127
|
+
"Generate speech audio from text using Google Cloud Text-to-Speech. "
|
|
128
|
+
"Supports multiple languages, voice models, and output formats. "
|
|
129
|
+
"Can process both plain text and Markdown content with natural speech synthesis."
|
|
130
|
+
)
|
|
131
|
+
args_schema = GoogleTTSArgs
|
|
132
|
+
|
|
133
|
+
# Voice model mappings by language and gender
|
|
134
|
+
VOICE_MODELS = {
|
|
135
|
+
"en-US": {
|
|
136
|
+
"MALE": "en-US-Neural2-D",
|
|
137
|
+
"FEMALE": "en-US-Neural2-F"
|
|
138
|
+
},
|
|
139
|
+
"es-ES": {
|
|
140
|
+
"MALE": "es-ES-Polyglot-1",
|
|
141
|
+
"FEMALE": "es-ES-Neural2-H"
|
|
142
|
+
},
|
|
143
|
+
"fr-FR": {
|
|
144
|
+
"MALE": "fr-FR-Neural2-G",
|
|
145
|
+
"FEMALE": "fr-FR-Neural2-F"
|
|
146
|
+
},
|
|
147
|
+
"de-DE": {
|
|
148
|
+
"MALE": "de-DE-Neural2-G",
|
|
149
|
+
"FEMALE": "de-DE-Neural2-F"
|
|
150
|
+
},
|
|
151
|
+
"cmn-CN": {
|
|
152
|
+
"MALE": "cmn-CN-Standard-B",
|
|
153
|
+
"FEMALE": "cmn-CN-Standard-D"
|
|
154
|
+
},
|
|
155
|
+
"zh-CN": {
|
|
156
|
+
"MALE": "cmn-CN-Standard-B",
|
|
157
|
+
"FEMALE": "cmn-CN-Standard-D"
|
|
158
|
+
},
|
|
159
|
+
"ja-JP": {
|
|
160
|
+
"MALE": "ja-JP-Neural2-C",
|
|
161
|
+
"FEMALE": "ja-JP-Neural2-B"
|
|
162
|
+
},
|
|
163
|
+
"ko-KR": {
|
|
164
|
+
"MALE": "ko-KR-Neural2-C",
|
|
165
|
+
"FEMALE": "ko-KR-Neural2-A"
|
|
166
|
+
},
|
|
167
|
+
"pt-BR": {
|
|
168
|
+
"MALE": "pt-BR-Neural2-B",
|
|
169
|
+
"FEMALE": "pt-BR-Neural2-A"
|
|
170
|
+
},
|
|
171
|
+
"it-IT": {
|
|
172
|
+
"MALE": "it-IT-Neural2-C",
|
|
173
|
+
"FEMALE": "it-IT-Neural2-A"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Audio format mappings
|
|
178
|
+
FORMAT_MAPPING = {
|
|
179
|
+
"OGG_OPUS": (texttospeech.AudioEncoding.OGG_OPUS, "ogg"),
|
|
180
|
+
"MP3": (texttospeech.AudioEncoding.MP3, "mp3"),
|
|
181
|
+
"LINEAR16": (texttospeech.AudioEncoding.LINEAR16, "wav"),
|
|
182
|
+
"MULAW": (texttospeech.AudioEncoding.MULAW, "wav"),
|
|
183
|
+
"ALAW": (texttospeech.AudioEncoding.ALAW, "wav"),
|
|
184
|
+
"PCM": (texttospeech.AudioEncoding.PCM, "pcm")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
def __init__(
|
|
188
|
+
self,
|
|
189
|
+
credentials_path: Optional[str] = None,
|
|
190
|
+
default_voice_model: str = "en-US-Neural2-F",
|
|
191
|
+
default_language: str = "en-US",
|
|
192
|
+
default_format: str = "OGG_OPUS",
|
|
193
|
+
use_long_audio_synthesis: bool = False,
|
|
194
|
+
**kwargs
|
|
195
|
+
):
|
|
196
|
+
"""
|
|
197
|
+
Initialize the Google TTS Tool.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
credentials_path: Path to Google Cloud service account JSON file
|
|
201
|
+
default_voice_model: Default voice model to use
|
|
202
|
+
default_language: Default language code
|
|
203
|
+
default_format: Default audio output format
|
|
204
|
+
use_long_audio_synthesis: Whether to use long audio synthesis for texts >5000 chars
|
|
205
|
+
**kwargs: Additional arguments for AbstractTool
|
|
206
|
+
"""
|
|
207
|
+
super().__init__(**kwargs)
|
|
208
|
+
|
|
209
|
+
# Set up credentials
|
|
210
|
+
self.credentials_path = credentials_path or os.getenv('GOOGLE_APPLICATION_CREDENTIALS')
|
|
211
|
+
if not self.credentials_path:
|
|
212
|
+
# Try common paths
|
|
213
|
+
possible_paths = [
|
|
214
|
+
Path.cwd() / "credentials" / "google-tts.json",
|
|
215
|
+
Path.cwd() / "env" / "google" / "tts-service.json",
|
|
216
|
+
Path(__file__).parent.parent / "credentials" / "google-tts.json"
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
for path in possible_paths:
|
|
220
|
+
if path.exists():
|
|
221
|
+
self.credentials_path = str(path)
|
|
222
|
+
break
|
|
223
|
+
|
|
224
|
+
if not self.credentials_path or not Path(self.credentials_path).exists():
|
|
225
|
+
raise ValueError(
|
|
226
|
+
"Google TTS credentials not found. Please provide credentials_path or set "
|
|
227
|
+
"GOOGLE_APPLICATION_CREDENTIALS environment variable."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Configuration
|
|
231
|
+
self.default_voice_model = default_voice_model
|
|
232
|
+
self.default_language = default_language
|
|
233
|
+
self.default_format = default_format
|
|
234
|
+
self.use_long_audio_synthesis = use_long_audio_synthesis
|
|
235
|
+
|
|
236
|
+
# Initialize credentials
|
|
237
|
+
try:
|
|
238
|
+
self.credentials = service_account.Credentials.from_service_account_file(
|
|
239
|
+
self.credentials_path
|
|
240
|
+
)
|
|
241
|
+
self.logger.info(f"Google TTS tool initialized with credentials from: {self.credentials_path}")
|
|
242
|
+
except Exception as e:
|
|
243
|
+
raise ValueError(f"Failed to load Google TTS credentials: {e}")
|
|
244
|
+
|
|
245
|
+
def _default_output_dir(self) -> Path:
|
|
246
|
+
"""Get the default output directory for TTS audio files."""
|
|
247
|
+
return self.static_dir / "audio" / "tts"
|
|
248
|
+
|
|
249
|
+
def _is_markdown(self, text: str) -> bool:
|
|
250
|
+
"""Determine if the text appears to be Markdown formatted."""
|
|
251
|
+
if not text or not isinstance(text, str):
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
text = text.strip()
|
|
255
|
+
if not text:
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
# Check first character for Markdown markers
|
|
259
|
+
first_char = text[0]
|
|
260
|
+
if first_char in "#*_>`-":
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
# Check if first character is a digit (for numbered lists)
|
|
264
|
+
if first_char.isdigit() and re.match(r'^\d+\.', text):
|
|
265
|
+
return True
|
|
266
|
+
|
|
267
|
+
# Check for common Markdown patterns
|
|
268
|
+
markdown_patterns = [
|
|
269
|
+
r"#{1,6}\s+", # Headers
|
|
270
|
+
r"\*\*.*?\*\*", # Bold
|
|
271
|
+
r"__.*?__", # Bold alternative
|
|
272
|
+
r"\*.*?\*", # Italic
|
|
273
|
+
r"_.*?_", # Italic alternative
|
|
274
|
+
r"`.*?`", # Inline code
|
|
275
|
+
r"\[.*?\]\(.*?\)", # Links
|
|
276
|
+
r"^\s*[\*\-\+]\s+", # Unordered lists
|
|
277
|
+
r"^\s*\d+\.\s+", # Ordered lists
|
|
278
|
+
r"```.*?```", # Code blocks
|
|
279
|
+
r"^\s*>\s+", # Blockquotes
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
for pattern in markdown_patterns:
|
|
283
|
+
if re.search(pattern, text, re.MULTILINE | re.DOTALL):
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
def _text_to_ssml(self, text: str) -> str:
|
|
289
|
+
"""Convert plain text to SSML."""
|
|
290
|
+
escaped_text = escape(text)
|
|
291
|
+
return f"<speak><p>{escaped_text}</p></speak>"
|
|
292
|
+
|
|
293
|
+
def _markdown_to_ssml(self, markdown_text: str) -> str:
|
|
294
|
+
"""Convert Markdown text to SSML for natural speech synthesis."""
|
|
295
|
+
# Handle code block prefixes
|
|
296
|
+
if markdown_text.startswith("```text"):
|
|
297
|
+
markdown_text = markdown_text[len("```text"):].strip()
|
|
298
|
+
|
|
299
|
+
ssml = "<speak>"
|
|
300
|
+
lines = markdown_text.split('\n')
|
|
301
|
+
in_code_block = False
|
|
302
|
+
|
|
303
|
+
for line in lines:
|
|
304
|
+
line = line.strip()
|
|
305
|
+
|
|
306
|
+
# Handle code blocks
|
|
307
|
+
if line.startswith("```"):
|
|
308
|
+
in_code_block = not in_code_block
|
|
309
|
+
if in_code_block:
|
|
310
|
+
ssml += '<prosody rate="x-slow"><p>'
|
|
311
|
+
else:
|
|
312
|
+
ssml += '</p></prosody>'
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
if in_code_block:
|
|
316
|
+
# Speak code slowly with pauses
|
|
317
|
+
ssml += escape(line) + '<break time="200ms"/>'
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
# Handle ellipses for dramatic pauses
|
|
321
|
+
if line == "...":
|
|
322
|
+
ssml += '<break time="1s"/>'
|
|
323
|
+
continue
|
|
324
|
+
|
|
325
|
+
# Handle Markdown headings
|
|
326
|
+
heading_match = re.match(r"^(#+)\s+(.*)", line)
|
|
327
|
+
if heading_match:
|
|
328
|
+
heading_level = len(heading_match.group(1))
|
|
329
|
+
heading_text = heading_match.group(2).strip()
|
|
330
|
+
|
|
331
|
+
# Vary emphasis based on heading level
|
|
332
|
+
if heading_level == 1:
|
|
333
|
+
ssml += f'<p><emphasis level="strong"><prosody rate="slow">{escape(heading_text)}</prosody></emphasis></p><break time="500ms"/>'
|
|
334
|
+
elif heading_level == 2:
|
|
335
|
+
ssml += f'<p><emphasis level="moderate">{escape(heading_text)}</emphasis></p><break time="300ms"/>'
|
|
336
|
+
else:
|
|
337
|
+
ssml += f'<p><emphasis level="reduced">{escape(heading_text)}</emphasis></p><break time="200ms"/>'
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
# Handle regular content
|
|
341
|
+
if line:
|
|
342
|
+
# Clean Markdown formatting for speech
|
|
343
|
+
clean_text = strip_markdown(line)
|
|
344
|
+
ssml += f'<p>{escape(clean_text)}</p>'
|
|
345
|
+
|
|
346
|
+
ssml += "</speak>"
|
|
347
|
+
return ssml
|
|
348
|
+
|
|
349
|
+
def _select_voice_model(self, voice_model: Optional[str], language_code: str, voice_gender: str) -> str:
|
|
350
|
+
"""Select appropriate voice model based on parameters."""
|
|
351
|
+
# If specific voice model provided, use it
|
|
352
|
+
if voice_model:
|
|
353
|
+
return voice_model
|
|
354
|
+
|
|
355
|
+
# Select voice based on language and gender
|
|
356
|
+
if language_code in self.VOICE_MODELS:
|
|
357
|
+
return self.VOICE_MODELS[language_code].get(voice_gender, self.default_voice_model)
|
|
358
|
+
|
|
359
|
+
# Fallback to default
|
|
360
|
+
self.logger.warning(f"No voice model found for {language_code}:{voice_gender}, using default")
|
|
361
|
+
return self.default_voice_model
|
|
362
|
+
|
|
363
|
+
def _get_audio_config(self, output_format: str, speaking_rate: float, pitch: float) -> tuple:
|
|
364
|
+
"""Get audio encoding configuration and file extension."""
|
|
365
|
+
if output_format not in self.FORMAT_MAPPING:
|
|
366
|
+
available_formats = ', '.join(self.FORMAT_MAPPING.keys())
|
|
367
|
+
raise ValueError(f"Unsupported output format: {output_format}. Available: {available_formats}")
|
|
368
|
+
|
|
369
|
+
encoding, extension = self.FORMAT_MAPPING[output_format]
|
|
370
|
+
|
|
371
|
+
audio_config = texttospeech.AudioConfig(
|
|
372
|
+
audio_encoding=encoding,
|
|
373
|
+
speaking_rate=speaking_rate,
|
|
374
|
+
pitch=pitch
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
return audio_config, extension
|
|
378
|
+
|
|
379
|
+
async def _synthesize_speech_short(
|
|
380
|
+
self,
|
|
381
|
+
text_input: str,
|
|
382
|
+
voice_model: str,
|
|
383
|
+
language_code: str,
|
|
384
|
+
audio_config: texttospeech.AudioConfig,
|
|
385
|
+
use_ssml: bool
|
|
386
|
+
) -> bytes:
|
|
387
|
+
"""Synthesize speech for shorter texts using standard API."""
|
|
388
|
+
try:
|
|
389
|
+
# Create async client
|
|
390
|
+
client = texttospeech.TextToSpeechAsyncClient(credentials=self.credentials)
|
|
391
|
+
|
|
392
|
+
# Prepare synthesis input
|
|
393
|
+
if use_ssml:
|
|
394
|
+
synthesis_input = texttospeech.SynthesisInput(ssml=text_input)
|
|
395
|
+
else:
|
|
396
|
+
synthesis_input = texttospeech.SynthesisInput(text=text_input)
|
|
397
|
+
|
|
398
|
+
# Configure voice
|
|
399
|
+
voice = texttospeech.VoiceSelectionParams(
|
|
400
|
+
language_code=language_code,
|
|
401
|
+
name=voice_model
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Make async request
|
|
405
|
+
response = await client.synthesize_speech(
|
|
406
|
+
input=synthesis_input,
|
|
407
|
+
voice=voice,
|
|
408
|
+
audio_config=audio_config
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
return response.audio_content
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
raise Exception(f"Speech synthesis failed: {str(e)}")
|
|
415
|
+
|
|
416
|
+
async def _synthesize_speech_long(
|
|
417
|
+
self,
|
|
418
|
+
text_input: str,
|
|
419
|
+
voice_model: str,
|
|
420
|
+
language_code: str,
|
|
421
|
+
audio_config: texttospeech.AudioConfig,
|
|
422
|
+
output_gcs_uri: str,
|
|
423
|
+
use_ssml: bool
|
|
424
|
+
) -> str:
|
|
425
|
+
"""Synthesize speech for longer texts using long audio synthesis API."""
|
|
426
|
+
try:
|
|
427
|
+
# Create long audio synthesis client
|
|
428
|
+
client = texttospeech.TextToSpeechLongAudioSynthesizeAsyncClient(
|
|
429
|
+
credentials=self.credentials
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Prepare synthesis input
|
|
433
|
+
if use_ssml:
|
|
434
|
+
synthesis_input = texttospeech.SynthesisInput(ssml=text_input)
|
|
435
|
+
else:
|
|
436
|
+
synthesis_input = texttospeech.SynthesisInput(text=text_input)
|
|
437
|
+
|
|
438
|
+
# Configure voice
|
|
439
|
+
voice = texttospeech.VoiceSelectionParams(
|
|
440
|
+
language_code=language_code,
|
|
441
|
+
name=voice_model
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Create request
|
|
445
|
+
request = texttospeech.SynthesizeLongAudioRequest(
|
|
446
|
+
input=synthesis_input,
|
|
447
|
+
audio_config=audio_config,
|
|
448
|
+
output_gcs_uri=output_gcs_uri,
|
|
449
|
+
voice=voice
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Make async request
|
|
453
|
+
operation = await client.synthesize_long_audio(request=request)
|
|
454
|
+
|
|
455
|
+
self.logger.info("Waiting for long audio synthesis to complete...")
|
|
456
|
+
response = await operation.result()
|
|
457
|
+
|
|
458
|
+
return output_gcs_uri
|
|
459
|
+
|
|
460
|
+
except Exception as e:
|
|
461
|
+
raise Exception(f"Long audio synthesis failed: {str(e)}")
|
|
462
|
+
|
|
463
|
+
async def _execute(
|
|
464
|
+
self,
|
|
465
|
+
text: str,
|
|
466
|
+
voice_model: Optional[str] = None,
|
|
467
|
+
voice_gender: str = "FEMALE",
|
|
468
|
+
language_code: str = "en-US",
|
|
469
|
+
output_format: str = "OGG_OPUS",
|
|
470
|
+
file_prefix: str = "podcast",
|
|
471
|
+
speaking_rate: float = 1.0,
|
|
472
|
+
pitch: float = 0.0,
|
|
473
|
+
use_ssml: bool = True,
|
|
474
|
+
**kwargs
|
|
475
|
+
) -> Dict[str, Any]:
|
|
476
|
+
"""
|
|
477
|
+
Execute text-to-speech conversion (AbstractTool interface).
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
text: Text content to convert to speech
|
|
481
|
+
voice_model: Specific voice model or None for auto-selection
|
|
482
|
+
voice_gender: Voice gender preference
|
|
483
|
+
language_code: Language code (BCP-47 format)
|
|
484
|
+
output_format: Audio output format
|
|
485
|
+
file_prefix: Output filename prefix
|
|
486
|
+
speaking_rate: Speech rate (0.25-4.0)
|
|
487
|
+
pitch: Voice pitch (-20.0-20.0)
|
|
488
|
+
use_ssml: Whether to use SSML processing
|
|
489
|
+
**kwargs: Additional arguments
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
Dictionary with synthesis results and file information
|
|
493
|
+
"""
|
|
494
|
+
try:
|
|
495
|
+
self.logger.info(f"Starting TTS synthesis: {len(text)} characters, {language_code}, {voice_gender}")
|
|
496
|
+
|
|
497
|
+
# Select voice model
|
|
498
|
+
selected_voice = self._select_voice_model(voice_model, language_code, voice_gender)
|
|
499
|
+
|
|
500
|
+
# Get audio configuration
|
|
501
|
+
audio_config, file_extension = self._get_audio_config(output_format, speaking_rate, pitch)
|
|
502
|
+
|
|
503
|
+
# Process text based on type and SSML preference
|
|
504
|
+
if use_ssml:
|
|
505
|
+
if self._is_markdown(text):
|
|
506
|
+
self.logger.info("Converting Markdown to SSML")
|
|
507
|
+
processed_text = self._markdown_to_ssml(text)
|
|
508
|
+
else:
|
|
509
|
+
self.logger.info("Converting plain text to SSML")
|
|
510
|
+
processed_text = self._text_to_ssml(text)
|
|
511
|
+
else:
|
|
512
|
+
processed_text = text
|
|
513
|
+
|
|
514
|
+
# Generate output filename
|
|
515
|
+
output_filename = self.generate_filename(
|
|
516
|
+
prefix=file_prefix,
|
|
517
|
+
extension=file_extension,
|
|
518
|
+
include_timestamp=True
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
# Ensure output directory exists
|
|
522
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
523
|
+
output_path = self.output_dir / output_filename
|
|
524
|
+
output_path = self.validate_output_path(output_path)
|
|
525
|
+
|
|
526
|
+
# Determine synthesis method based on text length
|
|
527
|
+
char_count = len(processed_text)
|
|
528
|
+
use_long_synthesis = self.use_long_audio_synthesis and char_count > 5000
|
|
529
|
+
|
|
530
|
+
if use_long_synthesis:
|
|
531
|
+
self.logger.info(f"Using long audio synthesis for {char_count} characters")
|
|
532
|
+
# Note: Long synthesis requires Google Cloud Storage
|
|
533
|
+
# For now, fall back to standard synthesis
|
|
534
|
+
self.logger.warning("Long audio synthesis requires GCS setup, falling back to standard synthesis")
|
|
535
|
+
use_long_synthesis = False
|
|
536
|
+
|
|
537
|
+
# Synthesize speech
|
|
538
|
+
self.logger.info(f"Synthesizing speech with voice: {selected_voice}")
|
|
539
|
+
|
|
540
|
+
audio_content = await self._synthesize_speech_short(
|
|
541
|
+
processed_text,
|
|
542
|
+
selected_voice,
|
|
543
|
+
language_code,
|
|
544
|
+
audio_config,
|
|
545
|
+
use_ssml
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Save audio file
|
|
549
|
+
self.logger.info(f"Saving audio to: {output_path}")
|
|
550
|
+
async with aiofiles.open(output_path, 'wb') as audio_file:
|
|
551
|
+
await audio_file.write(audio_content)
|
|
552
|
+
|
|
553
|
+
# Generate URLs
|
|
554
|
+
file_url = self.to_static_url(output_path)
|
|
555
|
+
relative_url = self.relative_url(file_url)
|
|
556
|
+
|
|
557
|
+
# Calculate file statistics
|
|
558
|
+
file_size = output_path.stat().st_size
|
|
559
|
+
duration_estimate = len(text.split()) / 2.5 # Rough estimate: ~150 WPM
|
|
560
|
+
|
|
561
|
+
result = {
|
|
562
|
+
"filename": output_filename,
|
|
563
|
+
"file_path": str(output_path),
|
|
564
|
+
"file_url": file_url,
|
|
565
|
+
"relative_url": relative_url,
|
|
566
|
+
"file_size": file_size,
|
|
567
|
+
"file_size_mb": round(file_size / (1024 * 1024), 2),
|
|
568
|
+
"synthesis_info": {
|
|
569
|
+
"voice_model": selected_voice,
|
|
570
|
+
"language_code": language_code,
|
|
571
|
+
"voice_gender": voice_gender,
|
|
572
|
+
"output_format": output_format,
|
|
573
|
+
"speaking_rate": speaking_rate,
|
|
574
|
+
"pitch": pitch,
|
|
575
|
+
"used_ssml": use_ssml,
|
|
576
|
+
"was_markdown": use_ssml and self._is_markdown(text),
|
|
577
|
+
"character_count": len(text),
|
|
578
|
+
"processed_character_count": len(processed_text),
|
|
579
|
+
"estimated_duration_seconds": round(duration_estimate * 60, 1)
|
|
580
|
+
},
|
|
581
|
+
"generation_info": {
|
|
582
|
+
"timestamp": datetime.now().isoformat(),
|
|
583
|
+
"synthesis_method": "long" if use_long_synthesis else "standard",
|
|
584
|
+
"output_dir": str(self.output_dir)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
self.logger.info(f"TTS synthesis completed: {file_size} bytes, ~{duration_estimate:.1f} minutes")
|
|
589
|
+
return result
|
|
590
|
+
|
|
591
|
+
except Exception as e:
|
|
592
|
+
self.logger.error(f"Error in TTS synthesis: {e}")
|
|
593
|
+
self.logger.error(traceback.format_exc())
|
|
594
|
+
raise
|
|
595
|
+
|
|
596
|
+
def execute_sync(
|
|
597
|
+
self,
|
|
598
|
+
text: str,
|
|
599
|
+
voice_model: Optional[str] = None,
|
|
600
|
+
voice_gender: str = "FEMALE",
|
|
601
|
+
language_code: str = "en-US",
|
|
602
|
+
output_format: str = "OGG_OPUS",
|
|
603
|
+
file_prefix: str = "podcast",
|
|
604
|
+
speaking_rate: float = 1.0,
|
|
605
|
+
pitch: float = 0.0,
|
|
606
|
+
use_ssml: bool = True
|
|
607
|
+
) -> Dict[str, Any]:
|
|
608
|
+
"""
|
|
609
|
+
Execute TTS synthesis synchronously.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
text: Text content to convert to speech
|
|
613
|
+
voice_model: Specific voice model
|
|
614
|
+
voice_gender: Voice gender preference
|
|
615
|
+
language_code: Language code
|
|
616
|
+
output_format: Audio output format
|
|
617
|
+
file_prefix: Output filename prefix
|
|
618
|
+
speaking_rate: Speech rate
|
|
619
|
+
pitch: Voice pitch
|
|
620
|
+
use_ssml: Whether to use SSML
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
Dictionary with synthesis results
|
|
624
|
+
"""
|
|
625
|
+
try:
|
|
626
|
+
loop = asyncio.get_running_loop()
|
|
627
|
+
task = loop.create_task(self.execute(
|
|
628
|
+
text=text,
|
|
629
|
+
voice_model=voice_model,
|
|
630
|
+
voice_gender=voice_gender,
|
|
631
|
+
language_code=language_code,
|
|
632
|
+
output_format=output_format,
|
|
633
|
+
file_prefix=file_prefix,
|
|
634
|
+
speaking_rate=speaking_rate,
|
|
635
|
+
pitch=pitch,
|
|
636
|
+
use_ssml=use_ssml
|
|
637
|
+
))
|
|
638
|
+
return task
|
|
639
|
+
except RuntimeError:
|
|
640
|
+
return asyncio.run(self.execute(
|
|
641
|
+
text=text,
|
|
642
|
+
voice_model=voice_model,
|
|
643
|
+
voice_gender=voice_gender,
|
|
644
|
+
language_code=language_code,
|
|
645
|
+
output_format=output_format,
|
|
646
|
+
file_prefix=file_prefix,
|
|
647
|
+
speaking_rate=speaking_rate,
|
|
648
|
+
pitch=pitch,
|
|
649
|
+
use_ssml=use_ssml
|
|
650
|
+
))
|
|
651
|
+
|
|
652
|
+
def get_available_voices(self, language_code: Optional[str] = None) -> Dict[str, Any]:
|
|
653
|
+
"""Get available voice models for a language or all languages."""
|
|
654
|
+
if language_code:
|
|
655
|
+
return self.VOICE_MODELS.get(language_code, {})
|
|
656
|
+
return self.VOICE_MODELS
|
|
657
|
+
|
|
658
|
+
def get_supported_formats(self) -> Dict[str, str]:
|
|
659
|
+
"""Get supported audio output formats."""
|
|
660
|
+
return {fmt: ext for fmt, (_, ext) in self.FORMAT_MAPPING.items()}
|
|
661
|
+
|
|
662
|
+
def preview_ssml(self, text: str) -> str:
|
|
663
|
+
"""Preview how text would be converted to SSML."""
|
|
664
|
+
try:
|
|
665
|
+
if self._is_markdown(text):
|
|
666
|
+
return self._markdown_to_ssml(text)
|
|
667
|
+
else:
|
|
668
|
+
return self._text_to_ssml(text)
|
|
669
|
+
except Exception as e:
|
|
670
|
+
self.logger.error(f"Error generating SSML preview: {e}")
|
|
671
|
+
return f"<speak><p>Error generating SSML: {e}</p></speak>"
|
|
672
|
+
|
|
673
|
+
def estimate_cost(self, text: str, use_ssml: bool = True) -> Dict[str, Any]:
|
|
674
|
+
"""Estimate the cost for TTS synthesis (rough calculation)."""
|
|
675
|
+
# Process text to get accurate character count
|
|
676
|
+
if use_ssml:
|
|
677
|
+
if self._is_markdown(text):
|
|
678
|
+
processed_text = self._markdown_to_ssml(text)
|
|
679
|
+
else:
|
|
680
|
+
processed_text = self._text_to_ssml(text)
|
|
681
|
+
else:
|
|
682
|
+
processed_text = text
|
|
683
|
+
|
|
684
|
+
char_count = len(processed_text)
|
|
685
|
+
|
|
686
|
+
# Google TTS pricing (as of 2024): $4 per 1M characters for Neural2 voices
|
|
687
|
+
cost_per_million_chars = 4.0
|
|
688
|
+
estimated_cost = (char_count / 1_000_000) * cost_per_million_chars
|
|
689
|
+
|
|
690
|
+
return {
|
|
691
|
+
"original_characters": len(text),
|
|
692
|
+
"processed_characters": char_count,
|
|
693
|
+
"estimated_cost_usd": round(estimated_cost, 4),
|
|
694
|
+
"cost_breakdown": f"${cost_per_million_chars}/1M characters for Neural2 voices"
|
|
695
|
+
}
|