databricks-tellr-app 0.4.2.dev13__tar.gz → 0.4.2.dev14__tar.gz
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.
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/PKG-INFO +1 -1
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/pyproject.toml +1 -1
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/export.py +36 -1
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/settings/design_systems.py +108 -14
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/agent_factory.py +53 -6
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/converter_jail/jail.py +14 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/converter_jail/pptx_runner.py +8 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/converter_jail/protocol.py +33 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/design_system_compiler.py +178 -17
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/design_system_service.py +73 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/html_to_pptx.py +182 -2
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/README.md +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/databricks_tellr_app/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/databricks_tellr_app/run.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/databricks_tellr_app.egg-info/requires.txt +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/databricks_tellr_app.egg-info/top_level.txt +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/setup.cfg +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/setup.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/_otel_bootstrap.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/fixtures/tour_demo_deck.json +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/main.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/mcp_auth.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/mcp_server.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/middleware/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/middleware/csrf.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/middleware/request_logging.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/middleware/security_headers.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/_authz.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/admin.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/admin_usage.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/agent_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/chat.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/deck_contributors.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/feedback.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/google_slides.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/images.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/local_version.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/profiles.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/sessions.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/settings/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/settings/contributors.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/settings/deck_prompts.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/settings/identities.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/settings/slide_styles.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/setup.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/slides.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/tools.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/tour.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/verification.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/version.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/agent_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/feedback.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/requests.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/responses.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/settings/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/settings/requests.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/settings/responses.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/schemas/streaming.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/chat_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/export_job_queue.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/feedback_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/job_queue.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/session_manager.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/session_naming.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/usage_events.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/services/usage_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/utils/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/utils/validation.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/config_loader.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/context_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/database.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/databricks_client.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/defaults.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/encryption.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/init_default_profile.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/lakebase.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/migrate_profiles_to_agent_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/mlflow_agent_spans.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/mlflow_tracing.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/otel_logging.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/permission_context.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/prompt_modules.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/settings_db.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/core/user_context.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/deck_contributor.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/design_system.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/encryption_key.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/feedback.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/genie_space.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/google_global_credentials.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/google_oauth_token.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/identity.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/image.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/profile.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/profile_contributor.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/prompts.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/request_log.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/session.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/slide_deck_prompt.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/slide_style_library.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/usage_event.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/models/user_preference.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/database/types.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/domain/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/domain/slide.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/domain/slide_deck.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/agent.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/config_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/config_validator.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/converter_jail/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/converter_jail/ast_guard.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/converter_jail/codeprep.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/converter_jail/gslides_runner.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/design_system_templates.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/drive_uploader.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/evaluation/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/evaluation/llm_judge.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/genie_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/google_slides_auth.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/google_slides_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/html_to_google_slides.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/identity_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/identity_providers/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/identity_providers/account_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/identity_providers/local_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/identity_providers/workspace_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/image_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/image_tools.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/permission_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/pptx_from_html_huashu.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/pptx_from_records.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/pptx_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/profile_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/streaming_callback.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/tools/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/tools/agent_bricks_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/tools/ds_asset_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/tools/genie_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/tools/mcp_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/tools/model_endpoint_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/tools/vector_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/validator.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/css_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/ds_asset_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/error_handling.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/html_safety.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/html_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/image_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/js_validator.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/logging_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/pi_filter.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/slide_hash.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/spotlight.py +0 -0
- {databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/utils/text_caps.py +0 -0
{databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/api/routes/export.py
RENAMED
|
@@ -102,8 +102,22 @@ def build_slide_html(slide: dict, slide_deck: dict) -> str:
|
|
|
102
102
|
}
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
+
# NO `crossorigin` — deliberately, and it must stay that way (WM-02).
|
|
106
|
+
#
|
|
107
|
+
# `crossorigin="anonymous"` turns these into CORS requests. cdn.tailwindcss.com
|
|
108
|
+
# answers with a 302 and no CORS headers, so the request failed with
|
|
109
|
+
# net::ERR_FAILED and Tailwind's Preflight never landed — measured consequence
|
|
110
|
+
# on a DS-OFF deck: h3 rendered 22px/600 in the exported file against
|
|
111
|
+
# 16px/400 on screen, up to 38.25 px of per-component drift and a heading
|
|
112
|
+
# losing a line to re-wrap. The frontend surfaces emit the same tag WITHOUT the
|
|
113
|
+
# attribute (see buildSlideDocument in frontend/src/services/slideDocument.ts)
|
|
114
|
+
# and it loads, so dropping it is parity with what the user actually saw.
|
|
115
|
+
#
|
|
116
|
+
# Safe to drop on its own because there is NO `integrity` attribute here: with
|
|
117
|
+
# SRI present, removing `crossorigin` would block the script outright and turn
|
|
118
|
+
# a 38 px drift into no styling at all.
|
|
105
119
|
scripts_html = "\n".join([
|
|
106
|
-
f' <script src="{src}"
|
|
120
|
+
f' <script src="{src}"></script>'
|
|
107
121
|
for src in external_scripts
|
|
108
122
|
])
|
|
109
123
|
|
|
@@ -148,6 +162,27 @@ def build_slide_html(slide: dict, slide_deck: dict) -> str:
|
|
|
148
162
|
font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
149
163
|
}}
|
|
150
164
|
{deck_css}
|
|
165
|
+
/* After deck CSS: the fixed-frame document owns its OWN box (WM-01).
|
|
166
|
+
The html/body rule above is emitted BEFORE the deck, so a deck-authored
|
|
167
|
+
`body {{ padding: 48px 0; gap: 48px }}` — real generator output — used to win
|
|
168
|
+
on order and translate the WHOLE slide down: `.slide` measured at y=48, and
|
|
169
|
+
the .pptx carried that offset on every positioned component (dy 46-48 px)
|
|
170
|
+
including the background rectangle, whose bottom then sat at 768 inside a
|
|
171
|
+
720 frame. So: a 48 px unpainted band at the top of every exported slide and
|
|
172
|
+
48 px of content pushed past the clip.
|
|
173
|
+
Restated HERE, after the deck, exactly as the frontend preview surfaces
|
|
174
|
+
already do it (SLIDE_PREVIEW_RESET_STYLE is appended after the deck CSS in
|
|
175
|
+
buildSlideDocument), so the export agrees with what the user saw on screen.
|
|
176
|
+
Deliberately NOT added to the shared SLIDE_ROOT_RESET_STYLE: that constant is
|
|
177
|
+
also injected into the standalone MULTI-slide export, whose scrolling layout
|
|
178
|
+
depends on `body {{ padding: 40px 20px }}`.
|
|
179
|
+
No `!important`, matching the frontend rule: it wins by ORDER at equal
|
|
180
|
+
specificity, so both surfaces treat a deck's `!important` body padding the
|
|
181
|
+
same way and cannot drift apart. */
|
|
182
|
+
html, body {{
|
|
183
|
+
margin: 0;
|
|
184
|
+
padding: 0;
|
|
185
|
+
}}
|
|
151
186
|
/* After deck CSS: flatten the slide root (outer margin / radius / shadow) —
|
|
152
187
|
inside this fixed 1280x720 overflow:hidden document a root margin
|
|
153
188
|
shifts content past the clip and truncates the export's bottom edge
|
|
@@ -25,6 +25,7 @@ import re
|
|
|
25
25
|
import unicodedata
|
|
26
26
|
from collections import OrderedDict
|
|
27
27
|
from typing import Any, List, Optional, cast
|
|
28
|
+
from urllib.parse import unquote
|
|
28
29
|
|
|
29
30
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, Response, UploadFile, status
|
|
30
31
|
from pydantic import BaseModel, Field
|
|
@@ -833,6 +834,60 @@ def set_default_design_system(ds_id: int, db: Session = Depends(get_db)):
|
|
|
833
834
|
)
|
|
834
835
|
|
|
835
836
|
|
|
837
|
+
# SDR-4437 HIGH-3: workspace-global library writes are admin-only. REMOVING the
|
|
838
|
+
# org default is the same org-wide state change as setting it, so it carries the
|
|
839
|
+
# same gate — authorship must not buy it.
|
|
840
|
+
@router.post(
|
|
841
|
+
"/{ds_id}/clear-default", response_model=DesignSystemDetail,
|
|
842
|
+
dependencies=[Depends(require_admin)],
|
|
843
|
+
)
|
|
844
|
+
def clear_default_design_system(ds_id: int, db: Session = Depends(get_db)):
|
|
845
|
+
"""Remove the org-wide default, returning the library to "no default".
|
|
846
|
+
|
|
847
|
+
The lifecycle used to be ASYMMETRIC: ``set-default`` could promote a system and
|
|
848
|
+
switch between systems, but nothing could withdraw the flag — so returning to
|
|
849
|
+
no-default meant DELETING the design system or promoting a different one, and
|
|
850
|
+
the legacy slide-style fallback was unreachable in practice. Generation then
|
|
851
|
+
resolves no design system (``get_default_design_system_id`` returns None) and
|
|
852
|
+
new sessions take the legacy path.
|
|
853
|
+
|
|
854
|
+
Idempotent: clearing a system that is not the default succeeds and changes
|
|
855
|
+
nothing, so a double-click cannot produce an error to interpret.
|
|
856
|
+
|
|
857
|
+
Deliberately asymmetric with ``set-default``, which refuses an INACTIVE row:
|
|
858
|
+
promoting a tombstone into org-wide state is wrong, but REMOVING org-wide state
|
|
859
|
+
is always the safe direction and must never be blocked — refusing it would
|
|
860
|
+
strand the flag with no way to withdraw it.
|
|
861
|
+
|
|
862
|
+
Clearing correctly UNFREEZES rename/delete for the row's author: the freeze in
|
|
863
|
+
:func:`_require_creator_or_admin` is a function of the LOADED row's
|
|
864
|
+
``is_default``, so it lifts when the flag does. That grants a non-admin nothing
|
|
865
|
+
new, because this route is itself admin-only.
|
|
866
|
+
"""
|
|
867
|
+
try:
|
|
868
|
+
ds = db.query(DesignSystem).filter(DesignSystem.id == ds_id).first()
|
|
869
|
+
if not ds:
|
|
870
|
+
raise HTTPException(
|
|
871
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
872
|
+
detail=f"Design system {ds_id} not found",
|
|
873
|
+
)
|
|
874
|
+
if ds.is_default:
|
|
875
|
+
ds.is_default = False
|
|
876
|
+
db.commit()
|
|
877
|
+
db.refresh(ds)
|
|
878
|
+
logger.info(f"Cleared default design system: {ds.name} (id={ds.id})")
|
|
879
|
+
return _detail(ds)
|
|
880
|
+
except HTTPException:
|
|
881
|
+
raise
|
|
882
|
+
except Exception as e:
|
|
883
|
+
db.rollback()
|
|
884
|
+
logger.error(f"Error clearing default design system {ds_id}: {e}", exc_info=True)
|
|
885
|
+
raise HTTPException(
|
|
886
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
887
|
+
detail="Failed to clear default design system",
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
|
|
836
891
|
def _template_thumbnail_url(ds_id: int, template_id: int) -> str:
|
|
837
892
|
return f"/api/settings/design-systems/{ds_id}/templates/{template_id}/thumbnail"
|
|
838
893
|
|
|
@@ -1180,6 +1235,35 @@ _TEXT_SOURCE_MIMES = frozenset(
|
|
|
1180
1235
|
# pass — only ever seen in double-encoding smuggling attempts, so reject outright.
|
|
1181
1236
|
_ENCODED_TRAVERSAL_RE = re.compile(r"%(2e|2f|5c)", re.IGNORECASE)
|
|
1182
1237
|
|
|
1238
|
+
# How many decode passes a value may need before it stops changing. A value still
|
|
1239
|
+
# mutating after this many is pathological, never a real bundle path, and is
|
|
1240
|
+
# refused rather than normalized (see :func:`_validated_file_path`).
|
|
1241
|
+
_MAX_DECODE_PASSES = 4
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def _has_canonical_shape(value: str) -> bool:
|
|
1245
|
+
"""True when ``value`` has the shape of a canonical bundle-relative path.
|
|
1246
|
+
|
|
1247
|
+
The SHAPE half of :func:`_validated_file_path`, factored out so the exact same
|
|
1248
|
+
rules can be re-applied to each decoded form of a request path — one source of
|
|
1249
|
+
truth, rather than a second, weaker copy of the checks.
|
|
1250
|
+
"""
|
|
1251
|
+
if not value or "\\" in value:
|
|
1252
|
+
return False
|
|
1253
|
+
# NUL / C0 control characters never appear in a legitimate stored path, and
|
|
1254
|
+
# NUL in particular must be rejected BEFORE the DB lookup: psycopg2 refuses
|
|
1255
|
+
# NUL in a bound parameter (ValueError), which would surface as a 500
|
|
1256
|
+
# instead of the uniform opaque 404 (SQLite masks this as a no-match 404).
|
|
1257
|
+
if any(ord(ch) < 0x20 for ch in value):
|
|
1258
|
+
return False
|
|
1259
|
+
if _ENCODED_TRAVERSAL_RE.search(value):
|
|
1260
|
+
return False
|
|
1261
|
+
if value.startswith("/") or re.match(r"^[A-Za-z]:", value):
|
|
1262
|
+
return False
|
|
1263
|
+
if any(segment in ("", ".", "..") for segment in value.split("/")):
|
|
1264
|
+
return False
|
|
1265
|
+
return True
|
|
1266
|
+
|
|
1183
1267
|
|
|
1184
1268
|
def _validated_file_path(raw: str) -> Optional[str]:
|
|
1185
1269
|
"""Return ``raw`` when it is a canonical bundle-relative path, else ``None``.
|
|
@@ -1190,22 +1274,32 @@ def _validated_file_path(raw: str) -> Optional[str]:
|
|
|
1190
1274
|
backslashes, absolute/drive paths, empty/``.``/``..`` segments, and lingering
|
|
1191
1275
|
percent-encoded traversal bytes all fail. Lookups are DB-exact within one
|
|
1192
1276
|
design system (no filesystem involved), so this is defence-in-depth.
|
|
1277
|
+
|
|
1278
|
+
MULTIPLY-ENCODED forms are refused too. ``_ENCODED_TRAVERSAL_RE`` matches a
|
|
1279
|
+
lingering ``%2e``/``%2f``/``%5c``, but in ``%252e`` the traversal is hidden
|
|
1280
|
+
behind its own encoded ``%``, so ONE pass of that check cannot see it. The
|
|
1281
|
+
value is therefore decoded repeatedly and every intermediate form must satisfy
|
|
1282
|
+
the same shape rules; a value that keeps decoding into a traversal is refused
|
|
1283
|
+
at any depth.
|
|
1284
|
+
|
|
1285
|
+
The refusal keys on TRAVERSAL, never on the ``%`` character: a literal ``%``
|
|
1286
|
+
is legitimate in a brand's own filename, and an escape that decodes to
|
|
1287
|
+
something harmless (``%41`` -> ``A``) stays accepted. Rejecting ``%`` outright
|
|
1288
|
+
would turn a brand's real file into a 404 — the same class of brand-hostile
|
|
1289
|
+
limit the uncapped-text columns exist to avoid.
|
|
1193
1290
|
"""
|
|
1194
|
-
if not raw
|
|
1195
|
-
return None
|
|
1196
|
-
# NUL / C0 control characters never appear in a legitimate stored path, and
|
|
1197
|
-
# NUL in particular must be rejected BEFORE the DB lookup: psycopg2 refuses
|
|
1198
|
-
# NUL in a bound parameter (ValueError), which would surface as a 500
|
|
1199
|
-
# instead of the uniform opaque 404 (SQLite masks this as a no-match 404).
|
|
1200
|
-
if any(ord(ch) < 0x20 for ch in raw):
|
|
1201
|
-
return None
|
|
1202
|
-
if _ENCODED_TRAVERSAL_RE.search(raw):
|
|
1203
|
-
return None
|
|
1204
|
-
if raw.startswith("/") or re.match(r"^[A-Za-z]:", raw):
|
|
1205
|
-
return None
|
|
1206
|
-
if any(segment in ("", ".", "..") for segment in raw.split("/")):
|
|
1291
|
+
if not _has_canonical_shape(raw):
|
|
1207
1292
|
return None
|
|
1208
|
-
|
|
1293
|
+
candidate = raw
|
|
1294
|
+
for _ in range(_MAX_DECODE_PASSES):
|
|
1295
|
+
decoded = unquote(candidate)
|
|
1296
|
+
if decoded == candidate:
|
|
1297
|
+
return raw
|
|
1298
|
+
if not _has_canonical_shape(decoded):
|
|
1299
|
+
return None
|
|
1300
|
+
candidate = decoded
|
|
1301
|
+
# Still changing after _MAX_DECODE_PASSES: refuse rather than keep unwrapping.
|
|
1302
|
+
return None
|
|
1209
1303
|
|
|
1210
1304
|
|
|
1211
1305
|
def _is_text_source(path: str, mime: str) -> bool:
|
{databricks_tellr_app-0.4.2.dev13 → databricks_tellr_app-0.4.2.dev14}/src/services/agent_factory.py
RENAMED
|
@@ -312,6 +312,44 @@ def _get_prompt_content(
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
|
|
315
|
+
def _design_system_is_active(design_system_id: int) -> bool:
|
|
316
|
+
"""True when that design system exists and is ACTIVE.
|
|
317
|
+
|
|
318
|
+
The tool gate below needs the same verdict the prompt branch reaches at
|
|
319
|
+
:func:`_get_prompt_content` (``.filter_by(id=…, is_active=True)``, else
|
|
320
|
+
*"Design system not found, using default"*), so both halves of a generation
|
|
321
|
+
request agree about whether a design system is in play.
|
|
322
|
+
|
|
323
|
+
NOT the same question as ``chat_service.resolve_active_design_system_id``,
|
|
324
|
+
despite the name: that resolves the ``{{ds-asset:ID}}`` scope for a deck
|
|
325
|
+
RESPONSE and deliberately still resolves a soft-deleted system's bytes, so
|
|
326
|
+
already-generated decks keep their fonts and images (the D7 retention
|
|
327
|
+
contract). This is the AUTHORING question — may new content be made against
|
|
328
|
+
this brand — and a tombstone answers no.
|
|
329
|
+
|
|
330
|
+
Fails CLOSED. A lookup error degrades exactly the way the prompt branch does
|
|
331
|
+
(no brand material) rather than advertising a tool whose own lookup would fail
|
|
332
|
+
in the same breath.
|
|
333
|
+
"""
|
|
334
|
+
try:
|
|
335
|
+
from src.core.database import get_db_session
|
|
336
|
+
from src.database.models import DesignSystem
|
|
337
|
+
|
|
338
|
+
with get_db_session() as db:
|
|
339
|
+
return (
|
|
340
|
+
db.query(DesignSystem.id)
|
|
341
|
+
.filter_by(id=design_system_id, is_active=True)
|
|
342
|
+
.first()
|
|
343
|
+
is not None
|
|
344
|
+
)
|
|
345
|
+
except Exception as e:
|
|
346
|
+
logger.error(
|
|
347
|
+
f"Failed to resolve design_system_id for tool registration: {e}",
|
|
348
|
+
extra={"design_system_id": design_system_id},
|
|
349
|
+
)
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
|
|
315
353
|
def _build_tools(
|
|
316
354
|
config: AgentConfig,
|
|
317
355
|
session_data: dict[str, Any],
|
|
@@ -351,12 +389,21 @@ def _build_tools(
|
|
|
351
389
|
)
|
|
352
390
|
tools.append(image_search_tool)
|
|
353
391
|
|
|
354
|
-
# Brand-asset search tool — ONLY when a design system is
|
|
355
|
-
#
|
|
356
|
-
#
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
|
|
392
|
+
# Brand-asset search tool — ONLY when a selected design system is ACTIVE.
|
|
393
|
+
# Bound to the design_system_id via closure so it surfaces only that system's
|
|
394
|
+
# assets; the compiled style's ASSET CONTRACT tells the model to call it. A
|
|
395
|
+
# pinned template_id (Phase 4) shapes prompt assembly only and never changes
|
|
396
|
+
# tool registration.
|
|
397
|
+
#
|
|
398
|
+
# The is_active half matters because a session keeps its pin after the design
|
|
399
|
+
# system is soft-deleted: on the id alone, generation got a fully working brand
|
|
400
|
+
# tool for a TOMBSTONE (measured: "Found 2 brand asset(s)" with embeddable
|
|
401
|
+
# handles) while the prompt branch, which does filter is_active, supplied no
|
|
402
|
+
# brand at all — so new content was authored against a deleted brand with none
|
|
403
|
+
# of its instructions. Both halves now answer the same question.
|
|
404
|
+
if config.design_system_id is not None and _design_system_is_active(
|
|
405
|
+
config.design_system_id
|
|
406
|
+
):
|
|
360
407
|
tools.append(build_ds_asset_tool(config.design_system_id))
|
|
361
408
|
|
|
362
409
|
genie_index = 0
|
|
@@ -43,6 +43,13 @@ class JailResult:
|
|
|
43
43
|
returncode: int
|
|
44
44
|
timed_out: bool
|
|
45
45
|
stderr_tail: str = ""
|
|
46
|
+
#: Per-slide outcomes reported by the trusted runner, in the order received:
|
|
47
|
+
#: True when the slide's generated code produced its slide, False when the
|
|
48
|
+
#: deterministic placeholder fallback was used. EMPTY when the runner reported
|
|
49
|
+
#: nothing (a runner with no result channel, or one that died before emitting) —
|
|
50
|
+
#: callers must treat empty as "unknown", never as "all failed", so an absent
|
|
51
|
+
#: channel cannot manufacture a failure.
|
|
52
|
+
slide_results: tuple = ()
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
@dataclass
|
|
@@ -177,8 +184,14 @@ def _spawn(
|
|
|
177
184
|
|
|
178
185
|
assert proc.stdout is not None
|
|
179
186
|
|
|
187
|
+
slide_results: List[bool] = []
|
|
188
|
+
|
|
180
189
|
def _drain_stdout():
|
|
181
190
|
for line in proc.stdout:
|
|
191
|
+
result = protocol.decode_slide_result(line)
|
|
192
|
+
if result is not None:
|
|
193
|
+
slide_results.append(result[1])
|
|
194
|
+
continue
|
|
182
195
|
decoded = protocol.decode_progress(line)
|
|
183
196
|
if decoded and progress_cb:
|
|
184
197
|
try:
|
|
@@ -207,6 +220,7 @@ def _spawn(
|
|
|
207
220
|
returncode=proc.returncode if proc.returncode is not None else -1,
|
|
208
221
|
timed_out=timed_out,
|
|
209
222
|
stderr_tail="".join(stderr_lines[-40:]),
|
|
223
|
+
slide_results=tuple(slide_results),
|
|
210
224
|
)
|
|
211
225
|
finally:
|
|
212
226
|
shutil.rmtree(env["HOME"], ignore_errors=True)
|
|
@@ -106,14 +106,22 @@ def run(job_dir: str, output_path: str, emit: Optional[Callable[[str], None]] =
|
|
|
106
106
|
sdir = Path(job_dir) / entry["dir"]
|
|
107
107
|
assets_dir = str(sdir / protocol.ASSETS_DIR)
|
|
108
108
|
|
|
109
|
+
# Report whether this slide got REAL content or the deterministic
|
|
110
|
+
# placeholder. Both fallback branches below are invisible in the saved
|
|
111
|
+
# file — it is a valid .pptx either way — so the host cannot otherwise
|
|
112
|
+
# tell an all-placeholder deck from a good one.
|
|
113
|
+
rendered = True
|
|
109
114
|
if not entry.get("has_code"):
|
|
110
115
|
_add_fallback(prs, i, f"Slide {i}")
|
|
116
|
+
rendered = False
|
|
111
117
|
else:
|
|
112
118
|
html_str = (sdir / protocol.HTML_NAME).read_text(encoding="utf-8")
|
|
113
119
|
code = (sdir / protocol.CODE_NAME).read_text(encoding="utf-8")
|
|
114
120
|
if not _exec_snippet(code, prs, html_str, assets_dir):
|
|
115
121
|
_add_fallback(prs, i, "Slide Content")
|
|
122
|
+
rendered = False
|
|
116
123
|
|
|
124
|
+
emit(protocol.encode_slide_result(entry["index"], rendered))
|
|
117
125
|
emit(protocol.encode_progress(i, total, f"Building slide {i}/{total}…"))
|
|
118
126
|
|
|
119
127
|
prs.save(output_path)
|
|
@@ -23,6 +23,39 @@ HTML_NAME = "html.txt"
|
|
|
23
23
|
ASSETS_DIR = "assets"
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
# Sentinel prefix for the trusted per-slide RESULT channel. Same trust property as
|
|
27
|
+
# PROGRESS_PREFIX and for the same reason: only the trusted runner emits it, and
|
|
28
|
+
# generated code's stdout is redirected to stderr before any snippet executes, so a
|
|
29
|
+
# snippet cannot forge a result for itself. The host needs this because whether a
|
|
30
|
+
# slide produced REAL content is only known after validation and execution — a
|
|
31
|
+
# non-empty snippet can still be rejected by the import allowlist or raise when it
|
|
32
|
+
# runs, and both outcomes fall back to a placeholder slide. Without a report, an
|
|
33
|
+
# all-placeholder deck is indistinguishable from a good one.
|
|
34
|
+
RESULT_PREFIX = "@@TELLR_JAIL_SLIDE_RESULT@@ "
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def encode_slide_result(index: int, rendered: bool) -> str:
|
|
38
|
+
"""Encode one per-slide outcome as a single stdout line (with newline).
|
|
39
|
+
|
|
40
|
+
``rendered`` is True only when the slide's own generated code ran and produced
|
|
41
|
+
its slide; False whenever the deterministic placeholder fallback was used.
|
|
42
|
+
"""
|
|
43
|
+
payload = json.dumps({"index": index, "rendered": bool(rendered)})
|
|
44
|
+
return f"{RESULT_PREFIX}{payload}\n"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def decode_slide_result(line: str):
|
|
48
|
+
"""Decode a stdout line into ``(index, rendered)``, or None when the line is
|
|
49
|
+
not a slide-result line / is malformed."""
|
|
50
|
+
if not line.startswith(RESULT_PREFIX):
|
|
51
|
+
return None
|
|
52
|
+
try:
|
|
53
|
+
data = json.loads(line[len(RESULT_PREFIX):].strip())
|
|
54
|
+
return int(data["index"]), bool(data["rendered"])
|
|
55
|
+
except (ValueError, KeyError, TypeError):
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
26
59
|
def encode_progress(current: int, total: int, message: str) -> str:
|
|
27
60
|
"""Encode one progress event as a single stdout line (with newline)."""
|
|
28
61
|
payload = json.dumps({"current": current, "total": total, "message": message})
|