databricks-tellr-app 0.4.3.dev1__tar.gz → 0.4.3.dev3__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.
Files changed (167) hide show
  1. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/PKG-INFO +1 -1
  2. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
  3. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/pyproject.toml +1 -1
  4. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/chat.py +2 -2
  5. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/export.py +4 -4
  6. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/google_slides.py +11 -9
  7. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/images.py +27 -45
  8. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/sessions.py +1 -1
  9. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/settings/identities.py +3 -3
  10. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/setup.py +2 -2
  11. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/slides.py +13 -13
  12. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/verification.py +3 -3
  13. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/requests.py +2 -2
  14. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/chat_service.py +9 -12
  15. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/database.py +0 -40
  16. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/image.py +1 -15
  17. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/image_service.py +9 -9
  18. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/image_tools.py +2 -2
  19. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/image_utils.py +4 -5
  20. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/README.md +0 -0
  21. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/databricks_tellr_app/__init__.py +0 -0
  22. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/databricks_tellr_app/run.py +0 -0
  23. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
  24. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  25. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/databricks_tellr_app.egg-info/requires.txt +0 -0
  26. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  27. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/setup.cfg +0 -0
  28. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/setup.py +0 -0
  29. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/__init__.py +0 -0
  30. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/__init__.py +0 -0
  31. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/_otel_bootstrap.py +0 -0
  32. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/fixtures/tour_demo_deck.json +0 -0
  33. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/main.py +0 -0
  34. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/mcp_auth.py +0 -0
  35. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/mcp_server.py +0 -0
  36. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/middleware/__init__.py +0 -0
  37. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/middleware/csrf.py +0 -0
  38. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/middleware/request_logging.py +0 -0
  39. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/middleware/security_headers.py +0 -0
  40. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/__init__.py +0 -0
  41. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/_authz.py +0 -0
  42. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/admin.py +0 -0
  43. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/admin_usage.py +0 -0
  44. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/agent_config.py +0 -0
  45. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/deck_contributors.py +0 -0
  46. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/feedback.py +0 -0
  47. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/local_version.py +0 -0
  48. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/profiles.py +0 -0
  49. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/settings/__init__.py +0 -0
  50. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/settings/contributors.py +0 -0
  51. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/settings/deck_prompts.py +0 -0
  52. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/settings/design_systems.py +0 -0
  53. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/settings/slide_styles.py +0 -0
  54. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/tools.py +0 -0
  55. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/tour.py +0 -0
  56. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/routes/version.py +0 -0
  57. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/__init__.py +0 -0
  58. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/agent_config.py +0 -0
  59. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/feedback.py +0 -0
  60. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/responses.py +0 -0
  61. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/settings/__init__.py +0 -0
  62. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/settings/requests.py +0 -0
  63. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/settings/responses.py +0 -0
  64. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/schemas/streaming.py +0 -0
  65. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/__init__.py +0 -0
  66. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/export_job_queue.py +0 -0
  67. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/feedback_service.py +0 -0
  68. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/job_queue.py +0 -0
  69. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/session_manager.py +0 -0
  70. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/session_naming.py +0 -0
  71. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/usage_events.py +0 -0
  72. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/services/usage_service.py +0 -0
  73. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/utils/__init__.py +0 -0
  74. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/api/utils/validation.py +0 -0
  75. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/__init__.py +0 -0
  76. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/config_loader.py +0 -0
  77. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/context_utils.py +0 -0
  78. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/databricks_client.py +0 -0
  79. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/defaults.py +0 -0
  80. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/encryption.py +0 -0
  81. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/init_default_profile.py +0 -0
  82. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/lakebase.py +0 -0
  83. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/migrate_profiles_to_agent_config.py +0 -0
  84. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/mlflow_agent_spans.py +0 -0
  85. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/mlflow_tracing.py +0 -0
  86. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/otel_logging.py +0 -0
  87. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/permission_context.py +0 -0
  88. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/prompt_modules.py +0 -0
  89. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/settings_db.py +0 -0
  90. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/core/user_context.py +0 -0
  91. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/__init__.py +0 -0
  92. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/__init__.py +0 -0
  93. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/deck_contributor.py +0 -0
  94. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/design_system.py +0 -0
  95. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/encryption_key.py +0 -0
  96. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/feedback.py +0 -0
  97. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/genie_space.py +0 -0
  98. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/google_global_credentials.py +0 -0
  99. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/google_oauth_token.py +0 -0
  100. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/identity.py +0 -0
  101. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/profile.py +0 -0
  102. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/profile_contributor.py +0 -0
  103. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/prompts.py +0 -0
  104. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/request_log.py +0 -0
  105. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/session.py +0 -0
  106. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/slide_deck_prompt.py +0 -0
  107. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/slide_style_library.py +0 -0
  108. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/usage_event.py +0 -0
  109. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/models/user_preference.py +0 -0
  110. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/database/types.py +0 -0
  111. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/domain/__init__.py +0 -0
  112. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/domain/slide.py +0 -0
  113. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/domain/slide_deck.py +0 -0
  114. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/__init__.py +0 -0
  115. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/agent.py +0 -0
  116. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/agent_factory.py +0 -0
  117. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/config_service.py +0 -0
  118. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/config_validator.py +0 -0
  119. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/converter_jail/__init__.py +0 -0
  120. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/converter_jail/ast_guard.py +0 -0
  121. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/converter_jail/codeprep.py +0 -0
  122. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/converter_jail/gslides_runner.py +0 -0
  123. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/converter_jail/jail.py +0 -0
  124. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/converter_jail/pptx_runner.py +0 -0
  125. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/converter_jail/protocol.py +0 -0
  126. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/design_system_compiler.py +0 -0
  127. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/design_system_service.py +0 -0
  128. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/design_system_templates.py +0 -0
  129. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/drive_uploader.py +0 -0
  130. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/evaluation/__init__.py +0 -0
  131. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/evaluation/llm_judge.py +0 -0
  132. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/genie_service.py +0 -0
  133. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/google_slides_auth.py +0 -0
  134. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/google_slides_prompts_defaults.py +0 -0
  135. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/html_to_google_slides.py +0 -0
  136. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/html_to_pptx.py +0 -0
  137. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/identity_provider.py +0 -0
  138. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/identity_providers/__init__.py +0 -0
  139. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/identity_providers/account_provider.py +0 -0
  140. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/identity_providers/local_provider.py +0 -0
  141. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/identity_providers/workspace_provider.py +0 -0
  142. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/permission_service.py +0 -0
  143. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/pptx_from_html_huashu.py +0 -0
  144. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/pptx_from_records.py +0 -0
  145. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/pptx_prompts_defaults.py +0 -0
  146. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/profile_service.py +0 -0
  147. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/streaming_callback.py +0 -0
  148. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/tools/__init__.py +0 -0
  149. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/tools/agent_bricks_tool.py +0 -0
  150. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/tools/ds_asset_tool.py +0 -0
  151. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/tools/genie_tool.py +0 -0
  152. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/tools/mcp_tool.py +0 -0
  153. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/tools/model_endpoint_tool.py +0 -0
  154. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/tools/vector_tool.py +0 -0
  155. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/services/validator.py +0 -0
  156. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/__init__.py +0 -0
  157. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/css_utils.py +0 -0
  158. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/ds_asset_utils.py +0 -0
  159. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/error_handling.py +0 -0
  160. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/html_safety.py +0 -0
  161. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/html_utils.py +0 -0
  162. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/js_validator.py +0 -0
  163. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/logging_config.py +0 -0
  164. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/pi_filter.py +0 -0
  165. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/slide_hash.py +0 -0
  166. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/spotlight.py +0 -0
  167. {databricks_tellr_app-0.4.3.dev1 → databricks_tellr_app-0.4.3.dev3}/src/utils/text_caps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev1
3
+ Version: 0.4.3.dev3
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev1
3
+ Version: 0.4.3.dev3
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-tellr-app"
7
- version = "0.4.3.dev1"
7
+ version = "0.4.3.dev3"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -384,7 +384,7 @@ async def send_message(
384
384
  logger.error(f"Chat request failed: {e}", exc_info=True)
385
385
  raise HTTPException(
386
386
  status_code=500,
387
- detail=f"Failed to process message: {str(e)}",
387
+ detail="Failed to process message",
388
388
  ) from e
389
389
  finally:
390
390
  await asyncio.to_thread(
@@ -662,7 +662,7 @@ async def submit_chat_async(
662
662
  session_manager.release_session_lock, request.session_id
663
663
  )
664
664
  logger.error(f"Failed to submit async chat: {e}")
665
- raise HTTPException(status_code=500, detail=str(e))
665
+ raise HTTPException(status_code=500, detail="Internal server error")
666
666
 
667
667
 
668
668
  @router.get("/chat/poll/{request_id}")
@@ -754,13 +754,13 @@ async def export_to_pptx(request: ExportPPTXRequest):
754
754
  temp_dir.rmdir()
755
755
  except Exception:
756
756
  pass
757
- raise HTTPException(status_code=500, detail=f"Export failed: {str(e)}")
757
+ raise HTTPException(status_code=500, detail="Export failed")
758
758
 
759
759
  except HTTPException:
760
760
  raise
761
761
  except Exception as e:
762
762
  logger.error("PPTX export failed", exc_info=True, extra={"error": str(e)})
763
- raise HTTPException(status_code=500, detail=f"Export failed: {str(e)}")
763
+ raise HTTPException(status_code=500, detail="Export failed")
764
764
 
765
765
 
766
766
  # =============================================================================
@@ -893,7 +893,7 @@ async def start_pptx_export_async(request: ExportPPTXRequest):
893
893
  except Exception as e:
894
894
  total_time = time.time() - start_time
895
895
  logger.error(f"Failed to start async export after {total_time:.2f}s: {e}", exc_info=True)
896
- raise HTTPException(status_code=500, detail=str(e))
896
+ raise HTTPException(status_code=500, detail="Failed to start export")
897
897
 
898
898
 
899
899
  @router.get("/pptx/poll/{job_id}", response_model=ExportJobResponse)
@@ -1038,7 +1038,7 @@ async def export_pptx_editable_from_records(request: ExportPPTXFromRecordsReques
1038
1038
  pptx_bytes = build_pptx(title, request.slides, font_mode=font_mode)
1039
1039
  except Exception as e:
1040
1040
  logger.exception(f"Editable PPTX emission failed: {e}")
1041
- raise HTTPException(status_code=500, detail=str(e))
1041
+ raise HTTPException(status_code=500, detail="Editable PPTX export failed")
1042
1042
 
1043
1043
  safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in title).strip("_") or "slides"
1044
1044
  from fastapi.responses import Response
@@ -198,17 +198,19 @@ async def auth_callback(
198
198
  "Google Slides OAuth callback successful",
199
199
  extra={"user": _get_user_identity()},
200
200
  )
201
- except Exception as exc:
201
+ except Exception:
202
+ # F-CR-2 (SDR-4437): never reflect the raw exception into the HTML page —
203
+ # it is logged server-side above. Show a static, generic failure message.
202
204
  logger.error("OAuth callback failed", exc_info=True)
203
205
  return HTMLResponse(
204
- content=f"""
206
+ content="""
205
207
  <html><body>
206
208
  <h2>Authorization Failed</h2>
207
- <p>{exc}</p>
209
+ <p>Authorization could not be completed. Please close this window and try again.</p>
208
210
  <script>
209
- if (window.opener) {{
210
- window.opener.postMessage({{ type: 'google-slides-auth', success: false }}, '*');
211
- }}
211
+ if (window.opener) {
212
+ window.opener.postMessage({ type: 'google-slides-auth', success: false }, '*');
213
+ }
212
214
  setTimeout(() => window.close(), 3000);
213
215
  </script>
214
216
  </body></html>
@@ -284,7 +286,7 @@ async def start_google_slides_export(
284
286
  slide_deck = chat_service.get_slides(request_body.session_id)
285
287
  except Exception as exc:
286
288
  logger.error("Failed to fetch slides", exc_info=True)
287
- raise HTTPException(status_code=500, detail=f"Failed to fetch slides: {exc}") from exc
289
+ raise HTTPException(status_code=500, detail="Failed to fetch slides") from exc
288
290
 
289
291
  if not slide_deck or not slide_deck.get("slides"):
290
292
  raise HTTPException(status_code=404, detail="No slides available")
@@ -448,7 +450,7 @@ async def export_google_slides_from_records(
448
450
  )
449
451
  except Exception as exc:
450
452
  logger.error("Drive upload failed", exc_info=True)
451
- raise HTTPException(status_code=502, detail=f"Drive upload failed: {exc}") from exc
453
+ raise HTTPException(status_code=502, detail="Drive upload failed") from exc
452
454
 
453
455
  session_manager.set_google_slides_info(
454
456
  session_id=request.session_id,
@@ -598,7 +600,7 @@ async def export_google_slides_from_huashu(
598
600
  logger.error("Drive upload failed (huashu path)", exc_info=True)
599
601
  raise HTTPException(
600
602
  status_code=502,
601
- detail=f"Drive upload failed: {exc}",
603
+ detail="Drive upload failed",
602
604
  ) from exc
603
605
 
604
606
  session_manager.set_google_slides_info(
@@ -1,5 +1,4 @@
1
1
  """Image upload and management API endpoints."""
2
- import base64
3
2
  import logging
4
3
  import os
5
4
  from typing import List, Optional
@@ -21,7 +20,7 @@ router = APIRouter(prefix="/api/images", tags=["images"])
21
20
 
22
21
  class ImageResponse(BaseModel):
23
22
  """Response schema for image metadata."""
24
- id: str # Opaque token (SDR-4437 F-TM-7) — the internal int PK is never exposed.
23
+ id: int
25
24
  filename: str
26
25
  original_filename: str
27
26
  mime_type: str
@@ -47,7 +46,7 @@ class ImageListResponse(BaseModel):
47
46
 
48
47
  class ImageDataResponse(BaseModel):
49
48
  """Response containing image base64 data."""
50
- id: str # Opaque token (SDR-4437 F-TM-7).
49
+ id: int
51
50
  mime_type: str
52
51
  base64_data: str
53
52
  data_uri: str # Ready-to-use: "data:image/png;base64,..."
@@ -81,7 +80,7 @@ def _get_current_user() -> str:
81
80
 
82
81
  def _image_to_response(img: ImageAsset) -> ImageResponse:
83
82
  return ImageResponse(
84
- id=img.token, # Expose the opaque token, never the enumerable int PK.
83
+ id=img.id,
85
84
  filename=img.filename,
86
85
  original_filename=img.original_filename,
87
86
  mime_type=img.mime_type,
@@ -168,75 +167,58 @@ def list_images(
168
167
  )
169
168
 
170
169
 
171
- def _deny_ephemeral_cross_user(image: ImageAsset) -> None:
172
- """Defense-in-depth (SDR-4437 F-TM-7): chat-pasted ("ephemeral") images are
173
- private to their uploader. Library images stay open-read. Raise 404 (not 403)
174
- so a wrong caller cannot even confirm the image exists.
175
- """
176
- if image.category == "ephemeral" and image.uploaded_by != _get_current_user():
177
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found")
178
-
179
-
180
- @router.get("/{token}", response_model=ImageResponse)
181
- def get_image(token: str, db: Session = Depends(get_db)):
182
- """Get image metadata by opaque token."""
170
+ @router.get("/{image_id}", response_model=ImageResponse)
171
+ def get_image(image_id: int, db: Session = Depends(get_db)):
172
+ """Get image metadata by ID."""
183
173
  try:
184
174
  image = db.query(ImageAsset).filter(
185
- ImageAsset.token == token,
175
+ ImageAsset.id == image_id,
186
176
  ImageAsset.is_active == True,
187
177
  ).first()
188
178
  if not image:
189
179
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found")
190
- _deny_ephemeral_cross_user(image)
191
180
  return _image_to_response(image)
192
181
  except HTTPException:
193
182
  raise
194
183
  except Exception as e:
195
- logger.error(f"Error getting image: {e}", exc_info=True)
184
+ logger.error(f"Error getting image {image_id}: {e}", exc_info=True)
196
185
  raise HTTPException(
197
186
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
198
187
  detail="Failed to get image",
199
188
  )
200
189
 
201
190
 
202
- @router.get("/{token}/data", response_model=ImageDataResponse)
203
- def get_image_data(token: str, db: Session = Depends(get_db)):
204
- """Get full image as base64 data (for embedding in slides), keyed by token."""
191
+ @router.get("/{image_id}/data", response_model=ImageDataResponse)
192
+ def get_image_data(image_id: int, db: Session = Depends(get_db)):
193
+ """Get full image as base64 data (for embedding in slides)."""
205
194
  try:
206
- image = db.query(ImageAsset).filter(
207
- ImageAsset.token == token,
208
- ImageAsset.is_active == True,
209
- ).first()
210
- if not image:
211
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found")
212
- _deny_ephemeral_cross_user(image)
213
- b64_data = base64.b64encode(image.image_data).decode("utf-8")
195
+ b64_data, mime_type = image_service.get_image_base64(db, image_id)
214
196
  return ImageDataResponse(
215
- id=image.token,
216
- mime_type=image.mime_type,
197
+ id=image_id,
198
+ mime_type=mime_type,
217
199
  base64_data=b64_data,
218
- data_uri=f"data:{image.mime_type};base64,{b64_data}",
200
+ data_uri=f"data:{mime_type};base64,{b64_data}",
219
201
  )
220
- except HTTPException:
221
- raise
202
+ except ValueError as e:
203
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
222
204
  except Exception as e:
223
- logger.error(f"Error getting image data: {e}", exc_info=True)
205
+ logger.error(f"Error getting image data {image_id}: {e}", exc_info=True)
224
206
  raise HTTPException(
225
207
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
226
208
  detail="Failed to get image data",
227
209
  )
228
210
 
229
211
 
230
- @router.put("/{token}", response_model=ImageResponse)
212
+ @router.put("/{image_id}", response_model=ImageResponse)
231
213
  def update_image(
232
- token: str,
214
+ image_id: int,
233
215
  request: ImageUpdateRequest,
234
216
  db: Session = Depends(get_db),
235
217
  ):
236
218
  """Update image metadata (tags, description, category)."""
237
219
  try:
238
220
  image = db.query(ImageAsset).filter(
239
- ImageAsset.token == token,
221
+ ImageAsset.id == image_id,
240
222
  ImageAsset.is_active == True,
241
223
  ).first()
242
224
  if not image:
@@ -264,20 +246,20 @@ def update_image(
264
246
  raise
265
247
  except Exception as e:
266
248
  db.rollback()
267
- logger.error(f"Error updating image: {e}", exc_info=True)
249
+ logger.error(f"Error updating image {image_id}: {e}", exc_info=True)
268
250
  raise HTTPException(
269
251
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
270
252
  detail="Failed to update image",
271
253
  )
272
254
 
273
255
 
274
- @router.delete("/{token}", status_code=status.HTTP_204_NO_CONTENT)
275
- def delete_image(token: str, db: Session = Depends(get_db)):
256
+ @router.delete("/{image_id}", status_code=status.HTTP_204_NO_CONTENT)
257
+ def delete_image(image_id: int, db: Session = Depends(get_db)):
276
258
  """Soft-delete an image."""
277
259
  try:
278
260
  user = _get_current_user()
279
261
  image = db.query(ImageAsset).filter(
280
- ImageAsset.token == token,
262
+ ImageAsset.id == image_id,
281
263
  ImageAsset.is_active == True, # noqa: E712
282
264
  ).first()
283
265
  if not image:
@@ -288,14 +270,14 @@ def delete_image(token: str, db: Session = Depends(get_db)):
288
270
  status_code=status.HTTP_403_FORBIDDEN,
289
271
  detail="You don't have permission to modify this image",
290
272
  )
291
- image_service.delete_image(db, token, user)
273
+ image_service.delete_image(db, image_id, user)
292
274
  except HTTPException:
293
275
  raise
294
276
  except ValueError as e:
295
277
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
296
278
  except Exception as e:
297
279
  db.rollback()
298
- logger.error(f"Error deleting image: {e}", exc_info=True)
280
+ logger.error(f"Error deleting image {image_id}: {e}", exc_info=True)
299
281
  raise HTTPException(
300
282
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
301
283
  detail="Failed to delete image",
@@ -553,7 +553,7 @@ async def duplicate_session(
553
553
  logger.error(f"Failed to duplicate session: {e}", exc_info=True)
554
554
  raise HTTPException(
555
555
  status_code=500,
556
- detail=f"Failed to duplicate session: {str(e)}",
556
+ detail="Failed to duplicate session",
557
557
  ) from e
558
558
 
559
559
 
@@ -109,7 +109,7 @@ def list_users(
109
109
  logger.error(f"Failed to list users: {e}", exc_info=True)
110
110
  raise HTTPException(
111
111
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
112
- detail=f"Failed to list users: {e}",
112
+ detail="Failed to list users",
113
113
  )
114
114
 
115
115
 
@@ -149,7 +149,7 @@ def list_groups(
149
149
  logger.error(f"Failed to list groups: {e}", exc_info=True)
150
150
  raise HTTPException(
151
151
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
152
- detail=f"Failed to list groups: {e}",
152
+ detail="Failed to list groups",
153
153
  )
154
154
 
155
155
 
@@ -203,5 +203,5 @@ def search_identities(
203
203
  logger.error(f"Failed to search identities: {e}", exc_info=True)
204
204
  raise HTTPException(
205
205
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
206
- detail=f"Failed to search identities: {e}",
206
+ detail="Failed to search identities",
207
207
  )
@@ -123,7 +123,7 @@ async def configure_workspace(request: ConfigureWorkspaceRequest):
123
123
  logger.error(f"Failed to configure workspace: {e}")
124
124
  raise HTTPException(
125
125
  status_code=500,
126
- detail=f"Failed to save configuration: {str(e)}"
126
+ detail="Failed to save configuration"
127
127
  )
128
128
 
129
129
 
@@ -154,5 +154,5 @@ async def test_connection():
154
154
  logger.error(f"Connection test failed: {e}")
155
155
  raise HTTPException(
156
156
  status_code=400,
157
- detail=f"Connection failed: {str(e)}"
157
+ detail="Connection failed"
158
158
  )
@@ -105,7 +105,7 @@ async def get_slides(
105
105
  raise
106
106
  except Exception as e:
107
107
  logger.error(f"Failed to get slides: {e}", exc_info=True)
108
- raise HTTPException(status_code=500, detail=str(e))
108
+ raise HTTPException(status_code=500, detail="Internal server error")
109
109
 
110
110
 
111
111
  @router.put("/reorder")
@@ -167,7 +167,7 @@ async def reorder_slides(request: ReorderRequest, db: Session = Depends(get_db))
167
167
  raise HTTPException(status_code=400, detail=str(e))
168
168
  except Exception as e:
169
169
  logger.error(f"Failed to reorder slides: {e}", exc_info=True)
170
- raise HTTPException(status_code=500, detail=str(e))
170
+ raise HTTPException(status_code=500, detail="Internal server error")
171
171
  finally:
172
172
  await asyncio.to_thread(
173
173
  session_manager.release_session_lock,
@@ -235,7 +235,7 @@ async def update_slide(index: int, request: UpdateSlideRequest, db: Session = De
235
235
  raise HTTPException(status_code=400, detail=str(e))
236
236
  except Exception as e:
237
237
  logger.error(f"Failed to update slide: {e}", exc_info=True)
238
- raise HTTPException(status_code=500, detail=str(e))
238
+ raise HTTPException(status_code=500, detail="Internal server error")
239
239
  finally:
240
240
  await asyncio.to_thread(
241
241
  session_manager.release_session_lock,
@@ -303,7 +303,7 @@ async def duplicate_slide(index: int, request: SlideActionRequest, db: Session =
303
303
  raise HTTPException(status_code=400, detail=str(e))
304
304
  except Exception as e:
305
305
  logger.error(f"Failed to duplicate slide: {e}", exc_info=True)
306
- raise HTTPException(status_code=500, detail=str(e))
306
+ raise HTTPException(status_code=500, detail="Internal server error")
307
307
  finally:
308
308
  await asyncio.to_thread(
309
309
  session_manager.release_session_lock,
@@ -377,7 +377,7 @@ async def delete_slide(
377
377
  raise HTTPException(status_code=400, detail=str(e))
378
378
  except Exception as e:
379
379
  logger.error(f"Failed to delete slide: {e}", exc_info=True)
380
- raise HTTPException(status_code=500, detail=str(e))
380
+ raise HTTPException(status_code=500, detail="Internal server error")
381
381
  finally:
382
382
  await asyncio.to_thread(
383
383
  session_manager.release_session_lock,
@@ -476,7 +476,7 @@ async def update_slide_verification(index: int, request: UpdateVerificationReque
476
476
  raise
477
477
  except Exception as e:
478
478
  logger.error(f"Failed to update slide verification: {e}", exc_info=True)
479
- raise HTTPException(status_code=500, detail=str(e))
479
+ raise HTTPException(status_code=500, detail="Internal server error")
480
480
 
481
481
 
482
482
  # ============================================================================
@@ -546,7 +546,7 @@ async def create_version(request: CreateVersionRequest):
546
546
  raise HTTPException(status_code=400, detail=str(e))
547
547
  except Exception as e:
548
548
  logger.error(f"Failed to create version: {e}", exc_info=True)
549
- raise HTTPException(status_code=500, detail=str(e))
549
+ raise HTTPException(status_code=500, detail="Internal server error")
550
550
 
551
551
 
552
552
  @router.patch("/versions/{version_number}/verification")
@@ -595,7 +595,7 @@ async def update_version_verification(
595
595
  raise HTTPException(status_code=400, detail=str(e))
596
596
  except Exception as e:
597
597
  logger.error(f"Failed to update version verification: {e}", exc_info=True)
598
- raise HTTPException(status_code=500, detail=str(e))
598
+ raise HTTPException(status_code=500, detail="Internal server error")
599
599
 
600
600
 
601
601
  class SyncVerificationRequest(BaseModel):
@@ -663,7 +663,7 @@ async def sync_latest_version_verification(request: SyncVerificationRequest):
663
663
 
664
664
  except Exception as e:
665
665
  logger.error(f"Failed to sync version verification: {e}", exc_info=True)
666
- raise HTTPException(status_code=500, detail=str(e))
666
+ raise HTTPException(status_code=500, detail="Internal server error")
667
667
 
668
668
 
669
669
  @router.get("/versions")
@@ -701,7 +701,7 @@ async def list_versions(session_id: str = Query(..., description="Session ID")):
701
701
  return {"versions": [], "current_version": None}
702
702
  except Exception as e:
703
703
  logger.error(f"Failed to list versions: {e}", exc_info=True)
704
- raise HTTPException(status_code=500, detail=str(e))
704
+ raise HTTPException(status_code=500, detail="Internal server error")
705
705
 
706
706
 
707
707
  # NOTE: must be registered BEFORE the parameterized "/versions/{version_number}"
@@ -738,7 +738,7 @@ async def get_current_version(session_id: str = Query(..., description="Session
738
738
  return {"current_version": None}
739
739
  except Exception as e:
740
740
  logger.error(f"Failed to get current version: {e}", exc_info=True)
741
- raise HTTPException(status_code=500, detail=str(e))
741
+ raise HTTPException(status_code=500, detail="Internal server error")
742
742
 
743
743
 
744
744
  @router.get("/versions/{version_number}")
@@ -789,7 +789,7 @@ async def preview_version(
789
789
  raise
790
790
  except Exception as e:
791
791
  logger.error(f"Failed to preview version: {e}", exc_info=True)
792
- raise HTTPException(status_code=500, detail=str(e))
792
+ raise HTTPException(status_code=500, detail="Internal server error")
793
793
 
794
794
 
795
795
  @router.post("/versions/{version_number}/restore")
@@ -859,7 +859,7 @@ async def restore_version(version_number: int, request: RestoreVersionRequest):
859
859
  raise HTTPException(status_code=400, detail=str(e))
860
860
  except Exception as e:
861
861
  logger.error(f"Failed to restore version: {e}", exc_info=True)
862
- raise HTTPException(status_code=500, detail=str(e))
862
+ raise HTTPException(status_code=500, detail="Internal server error")
863
863
  finally:
864
864
  await asyncio.to_thread(
865
865
  session_manager.release_session_lock,
@@ -291,7 +291,7 @@ async def verify_slide(slide_index: int, request: VerifySlideRequest):
291
291
  logger.error(f"Verification failed: {e}", exc_info=True)
292
292
  raise HTTPException(
293
293
  status_code=500,
294
- detail=f"Verification failed: {str(e)}",
294
+ detail="Verification failed",
295
295
  )
296
296
 
297
297
 
@@ -413,7 +413,7 @@ async def submit_feedback(slide_index: int, request: FeedbackRequest):
413
413
  logger.error(f"Failed to submit feedback: {e}", exc_info=True)
414
414
  raise HTTPException(
415
415
  status_code=500,
416
- detail=f"Failed to submit feedback: {str(e)}",
416
+ detail="Failed to submit feedback",
417
417
  )
418
418
 
419
419
 
@@ -541,6 +541,6 @@ async def get_genie_link(
541
541
  logger.error(f"Failed to get Genie link: {e}", exc_info=True)
542
542
  raise HTTPException(
543
543
  status_code=500,
544
- detail=f"Failed to get Genie link: {str(e)}",
544
+ detail="Failed to get Genie link",
545
545
  )
546
546
 
@@ -74,9 +74,9 @@ class ChatRequest(BaseModel):
74
74
  default=None,
75
75
  description="Optional context containing the slides to edit",
76
76
  )
77
- image_ids: Optional[list[str]] = Field(
77
+ image_ids: Optional[list[int]] = Field(
78
78
  default=None,
79
- description="Opaque tokens of images attached to this message (SDR-4437 F-TM-7)",
79
+ description="IDs of images attached to this message (from upload or paste)",
80
80
  )
81
81
  agent_config: Optional[dict] = Field(
82
82
  default=None,
@@ -345,7 +345,7 @@ class ChatService:
345
345
  session_id: str,
346
346
  message: str,
347
347
  slide_context: Optional[Dict[str, Any]] = None,
348
- image_ids: Optional[List[str]] = None,
348
+ image_ids: Optional[List[int]] = None,
349
349
  ) -> Dict[str, Any]:
350
350
  """Send a message to the agent and get response.
351
351
 
@@ -828,7 +828,7 @@ class ChatService:
828
828
  message: str,
829
829
  slide_context: Optional[Dict[str, Any]] = None,
830
830
  request_id: Optional[str] = None,
831
- image_ids: Optional[List[str]] = None,
831
+ image_ids: Optional[List[int]] = None,
832
832
  is_first_message_override: Optional[bool] = None,
833
833
  ) -> Generator[StreamEvent, None, None]:
834
834
  """Send a message and yield streaming events.
@@ -1993,30 +1993,27 @@ class ChatService:
1993
1993
 
1994
1994
  return ([], None)
1995
1995
 
1996
- def _inject_image_context(self, message: str, image_ids: List[str]) -> str:
1997
- """Append image metadata to user message so the agent knows about attached images.
1998
-
1999
- ``image_ids`` are opaque image tokens (SDR-4437 F-TM-7), not int PKs.
2000
- """
1996
+ def _inject_image_context(self, message: str, image_ids: List[int]) -> str:
1997
+ """Append image metadata to user message so the agent knows about attached images."""
2001
1998
  from src.core.database import get_db_session
2002
1999
  from src.services import image_service
2003
2000
 
2004
2001
  image_descriptions = []
2005
2002
  with get_db_session() as db:
2006
- for img_token in image_ids:
2003
+ for img_id in image_ids:
2007
2004
  try:
2008
2005
  img = db.query(image_service.ImageAsset).filter(
2009
- image_service.ImageAsset.token == img_token,
2006
+ image_service.ImageAsset.id == img_id,
2010
2007
  image_service.ImageAsset.is_active == True,
2011
2008
  ).first()
2012
2009
  if img:
2013
2010
  image_descriptions.append(
2014
- f'- Image ID {img.token}: "{img.original_filename}" '
2011
+ f'- Image ID {img.id}: "{img.original_filename}" '
2015
2012
  f'({img.description or "no description"}). '
2016
- f'Use: <img src="{{{{image:{img.token}}}}}" alt="{img.description or img.original_filename}" />'
2013
+ f'Use: <img src="{{{{image:{img.id}}}}}" alt="{img.description or img.original_filename}" />'
2017
2014
  )
2018
2015
  except Exception as e:
2019
- logger.warning(f"Failed to load image {img_token} for context injection: {e}")
2016
+ logger.warning(f"Failed to load image {img_id} for context injection: {e}")
2020
2017
 
2021
2018
  if not image_descriptions:
2022
2019
  return message
@@ -524,8 +524,6 @@ def _run_migrations(engine, schema: str | None = None):
524
524
  # --- image_assets.tags: json → jsonb (PostgreSQL) for native @> queries ---
525
525
  _migrate_image_assets_tags_json_to_jsonb(conn, schema, _qual, is_sqlite)
526
526
 
527
- # --- image_assets.token: unguessable external id + backfill (SDR-4437 F-TM-7) ---
528
- _migrate_image_assets_add_token(conn, inspector, schema, _qual, is_sqlite)
529
527
  # --- agent_config: style precedence in the DB, for the raw-SQL writers no
530
528
  # --- Python-side bind hook can see (PostgreSQL only; no-op on SQLite) ---
531
529
  _migrate_agent_config_precedence_trigger(
@@ -1060,44 +1058,6 @@ def _ensure_llm_judge_backend_default(
1060
1058
  )
1061
1059
 
1062
1060
 
1063
- def _migrate_image_assets_add_token(conn, inspector, schema, _qual, is_sqlite) -> None:
1064
- """Add the unguessable ``token`` column to image_assets and backfill (SDR-4437 F-TM-7).
1065
-
1066
- The int PK is enumerable, so image reads now key on an opaque token. Fresh
1067
- installs get the column via create_all(); existing tables need this ALTER +
1068
- per-row backfill. Idempotent: gated on the column's absence.
1069
- """
1070
- import secrets
1071
-
1072
- from sqlalchemy import text
1073
-
1074
- table = "image_assets"
1075
- try:
1076
- cols = {c["name"] for c in inspector.get_columns(table, schema=schema)}
1077
- except Exception:
1078
- return
1079
- if not cols or "token" in cols:
1080
- return
1081
-
1082
- q = _qual(table)
1083
- logger.info(f"Migration: adding token column to {table} (SDR-4437 F-TM-7)")
1084
- # Add nullable first so the ALTER succeeds on a populated table.
1085
- conn.execute(text(f"ALTER TABLE {q} ADD COLUMN token VARCHAR(64)"))
1086
-
1087
- # Backfill every existing row with a distinct unguessable token.
1088
- row_ids = [r[0] for r in conn.execute(text(f"SELECT id FROM {q} WHERE token IS NULL")).fetchall()]
1089
- for row_id in row_ids:
1090
- conn.execute(
1091
- text(f"UPDATE {q} SET token = :tok WHERE id = :id"),
1092
- {"tok": secrets.token_urlsafe(32), "id": row_id},
1093
- )
1094
-
1095
- # Enforce uniqueness (and non-null on PostgreSQL, now that every row has a token).
1096
- conn.execute(text(
1097
- f"CREATE UNIQUE INDEX IF NOT EXISTS ix_image_assets_token ON {q} (token)"
1098
- ))
1099
- if not is_sqlite:
1100
- conn.execute(text(f"ALTER TABLE {q} ALTER COLUMN token SET NOT NULL"))
1101
1061
  #: The one place the precedence rule is written in SQL. ``$$``-quoted so the body
1102
1062
  #: needs no escaping, and named after the rule rather than the tables, since both
1103
1063
  #: ``agent_config`` columns share it.
@@ -1,5 +1,4 @@
1
1
  """Image asset model — metadata and binary data stored together in Lakebase."""
2
- import secrets
3
2
  from datetime import datetime
4
3
 
5
4
  from sqlalchemy import Boolean, Column, DateTime, Integer, JSON, LargeBinary, String, Text
@@ -11,17 +10,6 @@ from src.core.database import Base
11
10
  _TagsColumn = JSON().with_variant(JSONB(), "postgresql")
12
11
 
13
12
 
14
- def _new_image_token() -> str:
15
- """Unguessable external identifier (SDR-4437 F-TM-7).
16
-
17
- The autoincrement int PK is enumerable, so it is never exposed. This
18
- opaque token is the only identifier that leaves the server — it is what
19
- the API returns, what chat sends in ``image_ids``, and what goes in
20
- ``{{image:...}}`` slide placeholders.
21
- """
22
- return secrets.token_urlsafe(32)
23
-
24
-
25
13
  class ImageAsset(Base):
26
14
  """Uploaded image with binary data stored directly in PostgreSQL.
27
15
 
@@ -32,9 +20,7 @@ class ImageAsset(Base):
32
20
 
33
21
  __tablename__ = "image_assets"
34
22
 
35
- id = Column(Integer, primary_key=True) # Internal only — never serialized.
36
- # Unguessable external identifier (SDR-4437 F-TM-7). token_urlsafe(32) → 43 chars.
37
- token = Column(String(64), unique=True, nullable=False, index=True, default=_new_image_token)
23
+ id = Column(Integer, primary_key=True)
38
24
  filename = Column(String(255), nullable=False) # Generated: {uuid}.{ext}
39
25
  original_filename = Column(String(255), nullable=False) # User's original filename
40
26
  mime_type = Column(String(50), nullable=False) # image/png, image/jpeg, image/gif, image/svg+xml