databricks-tellr-app 0.4.3.dev6__tar.gz → 0.4.3.dev7__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.dev6 → databricks_tellr_app-0.4.3.dev7}/PKG-INFO +1 -1
  2. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
  3. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/pyproject.toml +1 -1
  4. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/main.py +4 -2
  5. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/chat.py +2 -2
  6. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/export.py +4 -4
  7. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/google_slides.py +9 -11
  8. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/images.py +45 -27
  9. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/sessions.py +84 -4
  10. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/settings/identities.py +3 -3
  11. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/setup.py +2 -2
  12. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/slides.py +13 -13
  13. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/verification.py +3 -3
  14. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/requests.py +2 -2
  15. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/chat_service.py +12 -9
  16. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/database.py +158 -0
  17. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/image.py +15 -1
  18. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/image_service.py +9 -9
  19. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/image_tools.py +2 -2
  20. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/image_utils.py +5 -4
  21. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/README.md +0 -0
  22. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/databricks_tellr_app/__init__.py +0 -0
  23. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/databricks_tellr_app/run.py +0 -0
  24. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
  25. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  26. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/databricks_tellr_app.egg-info/requires.txt +0 -0
  27. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  28. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/setup.cfg +0 -0
  29. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/setup.py +0 -0
  30. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/__init__.py +0 -0
  31. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/__init__.py +0 -0
  32. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/_otel_bootstrap.py +0 -0
  33. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/fixtures/tour_demo_deck.json +0 -0
  34. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/mcp_auth.py +0 -0
  35. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/mcp_server.py +0 -0
  36. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/middleware/__init__.py +0 -0
  37. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/middleware/csrf.py +0 -0
  38. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/middleware/request_logging.py +0 -0
  39. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/middleware/security_headers.py +0 -0
  40. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/__init__.py +0 -0
  41. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/_authz.py +0 -0
  42. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/admin.py +0 -0
  43. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/admin_usage.py +0 -0
  44. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/agent_config.py +0 -0
  45. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/deck_contributors.py +0 -0
  46. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/feedback.py +0 -0
  47. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/local_version.py +0 -0
  48. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/profiles.py +0 -0
  49. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/settings/__init__.py +0 -0
  50. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/settings/contributors.py +0 -0
  51. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/settings/deck_prompts.py +0 -0
  52. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/settings/design_systems.py +0 -0
  53. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/settings/slide_styles.py +0 -0
  54. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/tools.py +0 -0
  55. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/tour.py +0 -0
  56. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/routes/version.py +0 -0
  57. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/__init__.py +0 -0
  58. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/agent_config.py +0 -0
  59. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/feedback.py +0 -0
  60. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/responses.py +0 -0
  61. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/settings/__init__.py +0 -0
  62. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/settings/requests.py +0 -0
  63. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/settings/responses.py +0 -0
  64. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/schemas/streaming.py +0 -0
  65. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/__init__.py +0 -0
  66. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/export_job_queue.py +0 -0
  67. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/feedback_service.py +0 -0
  68. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/job_queue.py +0 -0
  69. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/session_manager.py +0 -0
  70. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/session_naming.py +0 -0
  71. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/usage_events.py +0 -0
  72. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/services/usage_service.py +0 -0
  73. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/utils/__init__.py +0 -0
  74. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/api/utils/validation.py +0 -0
  75. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/__init__.py +0 -0
  76. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/config_loader.py +0 -0
  77. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/context_utils.py +0 -0
  78. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/databricks_client.py +0 -0
  79. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/defaults.py +0 -0
  80. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/encryption.py +0 -0
  81. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/init_default_profile.py +0 -0
  82. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/lakebase.py +0 -0
  83. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/migrate_profiles_to_agent_config.py +0 -0
  84. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/mlflow_agent_spans.py +0 -0
  85. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/mlflow_tracing.py +0 -0
  86. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/otel_logging.py +0 -0
  87. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/permission_context.py +0 -0
  88. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/prompt_modules.py +0 -0
  89. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/settings_db.py +0 -0
  90. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/core/user_context.py +0 -0
  91. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/__init__.py +0 -0
  92. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/__init__.py +0 -0
  93. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/deck_contributor.py +0 -0
  94. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/design_system.py +0 -0
  95. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/encryption_key.py +0 -0
  96. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/feedback.py +0 -0
  97. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/genie_space.py +0 -0
  98. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/google_global_credentials.py +0 -0
  99. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/google_oauth_token.py +0 -0
  100. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/identity.py +0 -0
  101. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/profile.py +0 -0
  102. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/profile_contributor.py +0 -0
  103. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/prompts.py +0 -0
  104. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/request_log.py +0 -0
  105. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/session.py +0 -0
  106. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/slide_deck_prompt.py +0 -0
  107. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/slide_style_library.py +0 -0
  108. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/usage_event.py +0 -0
  109. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/models/user_preference.py +0 -0
  110. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/database/types.py +0 -0
  111. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/domain/__init__.py +0 -0
  112. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/domain/slide.py +0 -0
  113. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/domain/slide_deck.py +0 -0
  114. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/__init__.py +0 -0
  115. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/agent.py +0 -0
  116. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/agent_factory.py +0 -0
  117. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/config_service.py +0 -0
  118. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/config_validator.py +0 -0
  119. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/converter_jail/__init__.py +0 -0
  120. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/converter_jail/ast_guard.py +0 -0
  121. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/converter_jail/codeprep.py +0 -0
  122. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/converter_jail/gslides_runner.py +0 -0
  123. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/converter_jail/jail.py +0 -0
  124. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/converter_jail/pptx_runner.py +0 -0
  125. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/converter_jail/protocol.py +0 -0
  126. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/design_system_compiler.py +0 -0
  127. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/design_system_service.py +0 -0
  128. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/design_system_templates.py +0 -0
  129. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/drive_uploader.py +0 -0
  130. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/evaluation/__init__.py +0 -0
  131. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/evaluation/llm_judge.py +0 -0
  132. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/genie_service.py +0 -0
  133. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/google_slides_auth.py +0 -0
  134. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/google_slides_prompts_defaults.py +0 -0
  135. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/html_to_google_slides.py +0 -0
  136. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/html_to_pptx.py +0 -0
  137. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/identity_provider.py +0 -0
  138. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/identity_providers/__init__.py +0 -0
  139. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/identity_providers/account_provider.py +0 -0
  140. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/identity_providers/local_provider.py +0 -0
  141. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/identity_providers/workspace_provider.py +0 -0
  142. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/permission_service.py +0 -0
  143. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/pptx_from_html_huashu.py +0 -0
  144. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/pptx_from_records.py +0 -0
  145. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/pptx_prompts_defaults.py +0 -0
  146. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/profile_service.py +0 -0
  147. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/streaming_callback.py +0 -0
  148. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/tools/__init__.py +0 -0
  149. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/tools/agent_bricks_tool.py +0 -0
  150. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/tools/ds_asset_tool.py +0 -0
  151. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/tools/genie_tool.py +0 -0
  152. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/tools/mcp_tool.py +0 -0
  153. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/tools/model_endpoint_tool.py +0 -0
  154. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/tools/vector_tool.py +0 -0
  155. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/services/validator.py +0 -0
  156. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/__init__.py +0 -0
  157. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/css_utils.py +0 -0
  158. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/ds_asset_utils.py +0 -0
  159. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/error_handling.py +0 -0
  160. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/html_safety.py +0 -0
  161. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/html_utils.py +0 -0
  162. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/js_validator.py +0 -0
  163. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/logging_config.py +0 -0
  164. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/pi_filter.py +0 -0
  165. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/slide_hash.py +0 -0
  166. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/src/utils/spotlight.py +0 -0
  167. {databricks_tellr_app-0.4.3.dev6 → databricks_tellr_app-0.4.3.dev7}/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.dev6
3
+ Version: 0.4.3.dev7
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.dev6
3
+ Version: 0.4.3.dev7
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.dev6"
7
+ version = "0.4.3.dev7"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -337,12 +337,14 @@ async def user_auth_middleware(request: Request, call_next):
337
337
  user_name = None
338
338
 
339
339
  if token:
340
- # Diagnostic logging: flag SP-vs-user tokens. F-CR-9 (SDR-4437): never log
341
- # any part of the bearer token (no prefix, no length) only derived booleans.
340
+ # Diagnostic logging: check if token is service principal ID (debug to avoid log spam on every request/poll)
341
+ token_prefix = token[:20] if len(token) > 20 else token
342
342
  is_sp_token = client_id and token.startswith(client_id)
343
343
  logger.debug(
344
344
  "OBO auth: extracted token from header",
345
345
  extra={
346
+ "token_prefix": token_prefix,
347
+ "token_length": len(token),
346
348
  "is_service_principal": is_sp_token,
347
349
  "header_present": True,
348
350
  },
@@ -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="Failed to process message",
387
+ detail=f"Failed to process message: {str(e)}",
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="Internal server error")
665
+ raise HTTPException(status_code=500, detail=str(e))
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="Export failed")
757
+ raise HTTPException(status_code=500, detail=f"Export failed: {str(e)}")
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="Export failed")
763
+ raise HTTPException(status_code=500, detail=f"Export failed: {str(e)}")
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="Failed to start export")
896
+ raise HTTPException(status_code=500, detail=str(e))
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="Editable PPTX export failed")
1041
+ raise HTTPException(status_code=500, detail=str(e))
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,19 +198,17 @@ async def auth_callback(
198
198
  "Google Slides OAuth callback successful",
199
199
  extra={"user": _get_user_identity()},
200
200
  )
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.
201
+ except Exception as exc:
204
202
  logger.error("OAuth callback failed", exc_info=True)
205
203
  return HTMLResponse(
206
- content="""
204
+ content=f"""
207
205
  <html><body>
208
206
  <h2>Authorization Failed</h2>
209
- <p>Authorization could not be completed. Please close this window and try again.</p>
207
+ <p>{exc}</p>
210
208
  <script>
211
- if (window.opener) {
212
- window.opener.postMessage({ type: 'google-slides-auth', success: false }, '*');
213
- }
209
+ if (window.opener) {{
210
+ window.opener.postMessage({{ type: 'google-slides-auth', success: false }}, '*');
211
+ }}
214
212
  setTimeout(() => window.close(), 3000);
215
213
  </script>
216
214
  </body></html>
@@ -286,7 +284,7 @@ async def start_google_slides_export(
286
284
  slide_deck = chat_service.get_slides(request_body.session_id)
287
285
  except Exception as exc:
288
286
  logger.error("Failed to fetch slides", exc_info=True)
289
- raise HTTPException(status_code=500, detail="Failed to fetch slides") from exc
287
+ raise HTTPException(status_code=500, detail=f"Failed to fetch slides: {exc}") from exc
290
288
 
291
289
  if not slide_deck or not slide_deck.get("slides"):
292
290
  raise HTTPException(status_code=404, detail="No slides available")
@@ -450,7 +448,7 @@ async def export_google_slides_from_records(
450
448
  )
451
449
  except Exception as exc:
452
450
  logger.error("Drive upload failed", exc_info=True)
453
- raise HTTPException(status_code=502, detail="Drive upload failed") from exc
451
+ raise HTTPException(status_code=502, detail=f"Drive upload failed: {exc}") from exc
454
452
 
455
453
  session_manager.set_google_slides_info(
456
454
  session_id=request.session_id,
@@ -600,7 +598,7 @@ async def export_google_slides_from_huashu(
600
598
  logger.error("Drive upload failed (huashu path)", exc_info=True)
601
599
  raise HTTPException(
602
600
  status_code=502,
603
- detail="Drive upload failed",
601
+ detail=f"Drive upload failed: {exc}",
604
602
  ) from exc
605
603
 
606
604
  session_manager.set_google_slides_info(
@@ -1,4 +1,5 @@
1
1
  """Image upload and management API endpoints."""
2
+ import base64
2
3
  import logging
3
4
  import os
4
5
  from typing import List, Optional
@@ -20,7 +21,7 @@ router = APIRouter(prefix="/api/images", tags=["images"])
20
21
 
21
22
  class ImageResponse(BaseModel):
22
23
  """Response schema for image metadata."""
23
- id: int
24
+ id: str # Opaque token (SDR-4437 F-TM-7) — the internal int PK is never exposed.
24
25
  filename: str
25
26
  original_filename: str
26
27
  mime_type: str
@@ -46,7 +47,7 @@ class ImageListResponse(BaseModel):
46
47
 
47
48
  class ImageDataResponse(BaseModel):
48
49
  """Response containing image base64 data."""
49
- id: int
50
+ id: str # Opaque token (SDR-4437 F-TM-7).
50
51
  mime_type: str
51
52
  base64_data: str
52
53
  data_uri: str # Ready-to-use: "data:image/png;base64,..."
@@ -80,7 +81,7 @@ def _get_current_user() -> str:
80
81
 
81
82
  def _image_to_response(img: ImageAsset) -> ImageResponse:
82
83
  return ImageResponse(
83
- id=img.id,
84
+ id=img.token, # Expose the opaque token, never the enumerable int PK.
84
85
  filename=img.filename,
85
86
  original_filename=img.original_filename,
86
87
  mime_type=img.mime_type,
@@ -167,58 +168,75 @@ def list_images(
167
168
  )
168
169
 
169
170
 
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."""
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."""
173
183
  try:
174
184
  image = db.query(ImageAsset).filter(
175
- ImageAsset.id == image_id,
185
+ ImageAsset.token == token,
176
186
  ImageAsset.is_active == True,
177
187
  ).first()
178
188
  if not image:
179
189
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found")
190
+ _deny_ephemeral_cross_user(image)
180
191
  return _image_to_response(image)
181
192
  except HTTPException:
182
193
  raise
183
194
  except Exception as e:
184
- logger.error(f"Error getting image {image_id}: {e}", exc_info=True)
195
+ logger.error(f"Error getting image: {e}", exc_info=True)
185
196
  raise HTTPException(
186
197
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
187
198
  detail="Failed to get image",
188
199
  )
189
200
 
190
201
 
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)."""
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."""
194
205
  try:
195
- b64_data, mime_type = image_service.get_image_base64(db, image_id)
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")
196
214
  return ImageDataResponse(
197
- id=image_id,
198
- mime_type=mime_type,
215
+ id=image.token,
216
+ mime_type=image.mime_type,
199
217
  base64_data=b64_data,
200
- data_uri=f"data:{mime_type};base64,{b64_data}",
218
+ data_uri=f"data:{image.mime_type};base64,{b64_data}",
201
219
  )
202
- except ValueError as e:
203
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
220
+ except HTTPException:
221
+ raise
204
222
  except Exception as e:
205
- logger.error(f"Error getting image data {image_id}: {e}", exc_info=True)
223
+ logger.error(f"Error getting image data: {e}", exc_info=True)
206
224
  raise HTTPException(
207
225
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
208
226
  detail="Failed to get image data",
209
227
  )
210
228
 
211
229
 
212
- @router.put("/{image_id}", response_model=ImageResponse)
230
+ @router.put("/{token}", response_model=ImageResponse)
213
231
  def update_image(
214
- image_id: int,
232
+ token: str,
215
233
  request: ImageUpdateRequest,
216
234
  db: Session = Depends(get_db),
217
235
  ):
218
236
  """Update image metadata (tags, description, category)."""
219
237
  try:
220
238
  image = db.query(ImageAsset).filter(
221
- ImageAsset.id == image_id,
239
+ ImageAsset.token == token,
222
240
  ImageAsset.is_active == True,
223
241
  ).first()
224
242
  if not image:
@@ -246,20 +264,20 @@ def update_image(
246
264
  raise
247
265
  except Exception as e:
248
266
  db.rollback()
249
- logger.error(f"Error updating image {image_id}: {e}", exc_info=True)
267
+ logger.error(f"Error updating image: {e}", exc_info=True)
250
268
  raise HTTPException(
251
269
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
252
270
  detail="Failed to update image",
253
271
  )
254
272
 
255
273
 
256
- @router.delete("/{image_id}", status_code=status.HTTP_204_NO_CONTENT)
257
- def delete_image(image_id: int, db: Session = Depends(get_db)):
274
+ @router.delete("/{token}", status_code=status.HTTP_204_NO_CONTENT)
275
+ def delete_image(token: str, db: Session = Depends(get_db)):
258
276
  """Soft-delete an image."""
259
277
  try:
260
278
  user = _get_current_user()
261
279
  image = db.query(ImageAsset).filter(
262
- ImageAsset.id == image_id,
280
+ ImageAsset.token == token,
263
281
  ImageAsset.is_active == True, # noqa: E712
264
282
  ).first()
265
283
  if not image:
@@ -270,14 +288,14 @@ def delete_image(image_id: int, db: Session = Depends(get_db)):
270
288
  status_code=status.HTTP_403_FORBIDDEN,
271
289
  detail="You don't have permission to modify this image",
272
290
  )
273
- image_service.delete_image(db, image_id, user)
291
+ image_service.delete_image(db, token, user)
274
292
  except HTTPException:
275
293
  raise
276
294
  except ValueError as e:
277
295
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
278
296
  except Exception as e:
279
297
  db.rollback()
280
- logger.error(f"Error deleting image {image_id}: {e}", exc_info=True)
298
+ logger.error(f"Error deleting image: {e}", exc_info=True)
281
299
  raise HTTPException(
282
300
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
283
301
  detail="Failed to delete image",
@@ -9,7 +9,10 @@ Session access is controlled by:
9
9
  """
10
10
 
11
11
  import asyncio
12
+ import json
12
13
  import logging
14
+ from datetime import datetime
15
+ from pathlib import Path
13
16
  from typing import Optional
14
17
 
15
18
  from fastapi import APIRouter, Depends, HTTPException, Query
@@ -550,7 +553,7 @@ async def duplicate_session(
550
553
  logger.error(f"Failed to duplicate session: {e}", exc_info=True)
551
554
  raise HTTPException(
552
555
  status_code=500,
553
- detail="Failed to duplicate session",
556
+ detail=f"Failed to duplicate session: {str(e)}",
554
557
  ) from e
555
558
 
556
559
 
@@ -766,9 +769,86 @@ async def cleanup_expired_sessions():
766
769
  ) from e
767
770
 
768
771
 
769
- # F-CR-10 (SDR-4437): the debug-only POST /{session_id}/export endpoint was
770
- # removed — it was unused by the frontend and dumped full session data (all chat
771
- # messages + deck) to logs/sessions/{id}.json.
772
+ @router.post("/{session_id}/export")
773
+ async def export_session(session_id: str):
774
+ """Export full session data to logs/sessions/{session_id}.json for debugging.
775
+
776
+ Exports:
777
+ - Session info (id, timestamps, profile)
778
+ - All chat messages (user + AI responses with full content)
779
+ - Full slide deck (HTML, scripts, slide count)
780
+
781
+ Args:
782
+ session_id: Session to export
783
+
784
+ Returns:
785
+ Export confirmation with file path
786
+ """
787
+ try:
788
+ # Permission check: require CAN_VIEW on the deck
789
+ await asyncio.to_thread(
790
+ _check_deck_permission_for_session, session_id, PermissionLevel.CAN_VIEW
791
+ )
792
+
793
+ session_manager = get_session_manager()
794
+
795
+ # Get all session data
796
+ session = await asyncio.to_thread(session_manager.get_session, session_id)
797
+ messages = await asyncio.to_thread(session_manager.get_messages, session_id)
798
+ slide_deck = await asyncio.to_thread(session_manager.get_slide_deck, session_id)
799
+
800
+ # Build export data
801
+ export_data = {
802
+ "exported_at": datetime.utcnow().isoformat(),
803
+ "session": session,
804
+ "messages": messages,
805
+ "slide_deck": slide_deck,
806
+ "summary": {
807
+ "message_count": len(messages),
808
+ "slide_count": slide_deck.get("slide_count", 0) if slide_deck else 0,
809
+ "user_messages": sum(1 for m in messages if m.get("role") == "user"),
810
+ "ai_messages": sum(1 for m in messages if m.get("role") == "assistant"),
811
+ }
812
+ }
813
+
814
+ # Ensure logs/sessions directory exists
815
+ sessions_dir = Path("logs/sessions")
816
+ sessions_dir.mkdir(parents=True, exist_ok=True)
817
+
818
+ # Write to file
819
+ export_path = sessions_dir / f"{session_id}.json"
820
+ with open(export_path, "w", encoding="utf-8") as f:
821
+ json.dump(export_data, f, indent=2, default=str)
822
+
823
+ logger.info(
824
+ "Session exported",
825
+ extra={
826
+ "session_id": session_id,
827
+ "export_path": str(export_path),
828
+ "message_count": len(messages),
829
+ },
830
+ )
831
+
832
+ return {
833
+ "status": "exported",
834
+ "session_id": session_id,
835
+ "export_path": str(export_path),
836
+ "summary": export_data["summary"],
837
+ }
838
+
839
+ except SessionNotFoundError:
840
+ raise HTTPException(
841
+ status_code=404,
842
+ detail=f"Session not found: {session_id}",
843
+ )
844
+ except HTTPException:
845
+ raise
846
+ except Exception as e:
847
+ logger.error(f"Failed to export session: {e}", exc_info=True)
848
+ raise HTTPException(
849
+ status_code=500,
850
+ detail="Export failed",
851
+ ) from e
772
852
 
773
853
 
774
854
  # =========================================================================
@@ -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="Failed to list users",
112
+ detail=f"Failed to list users: {e}",
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="Failed to list groups",
152
+ detail=f"Failed to list groups: {e}",
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="Failed to search identities",
206
+ detail=f"Failed to search identities: {e}",
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="Failed to save configuration"
126
+ detail=f"Failed to save configuration: {str(e)}"
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="Connection failed"
157
+ detail=f"Connection failed: {str(e)}"
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="Internal server error")
108
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
170
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
238
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
306
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
380
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
479
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
549
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
598
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
666
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
704
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
741
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
792
+ raise HTTPException(status_code=500, detail=str(e))
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="Internal server error")
862
+ raise HTTPException(status_code=500, detail=str(e))
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="Verification failed",
294
+ detail=f"Verification failed: {str(e)}",
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="Failed to submit feedback",
416
+ detail=f"Failed to submit feedback: {str(e)}",
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="Failed to get Genie link",
544
+ detail=f"Failed to get Genie link: {str(e)}",
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[int]] = Field(
77
+ image_ids: Optional[list[str]] = Field(
78
78
  default=None,
79
- description="IDs of images attached to this message (from upload or paste)",
79
+ description="Opaque tokens of images attached to this message (SDR-4437 F-TM-7)",
80
80
  )
81
81
  agent_config: Optional[dict] = Field(
82
82
  default=None,