django-spire 0.18.3__py3-none-any.whl → 0.19.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. django_spire/ai/admin.py +1 -0
  2. django_spire/ai/chat/apps.py +1 -8
  3. django_spire/ai/chat/intelligence/decoders/tools.py +29 -0
  4. django_spire/ai/chat/intelligence/prompts.py +13 -7
  5. django_spire/ai/chat/intelligence/workflows/chat_workflow.py +55 -57
  6. django_spire/ai/chat/models.py +2 -1
  7. django_spire/ai/chat/querysets.py +1 -3
  8. django_spire/ai/chat/responses.py +5 -0
  9. django_spire/ai/chat/templates/django_spire/ai/chat/card/chat_card.html +2 -2
  10. django_spire/ai/chat/templates/django_spire/ai/chat/dropdown/ellipsis_dropdown.html +1 -1
  11. django_spire/ai/chat/templates/django_spire/ai/chat/element/recent_chat_select_element.html +82 -44
  12. django_spire/ai/chat/templates/django_spire/ai/chat/message/loading_response_message.html +9 -4
  13. django_spire/ai/chat/templates/django_spire/ai/chat/message/message.html +33 -33
  14. django_spire/ai/chat/templates/django_spire/ai/chat/message/request_message.html +5 -8
  15. django_spire/ai/chat/templates/django_spire/ai/chat/message/response_message.html +14 -10
  16. django_spire/ai/chat/templates/django_spire/ai/chat/page/chat_page.html +35 -0
  17. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +72 -24
  18. django_spire/ai/chat/templates/django_spire/ai/chat/widget/selection_widget.html +42 -0
  19. django_spire/ai/chat/tests/test_urls/test_json_urls.py +4 -2
  20. django_spire/ai/chat/urls/__init__.py +1 -1
  21. django_spire/ai/chat/urls/json_urls.py +1 -1
  22. django_spire/ai/chat/urls/page_urls.py +1 -1
  23. django_spire/ai/chat/urls/{template_urls.py → template/template_urls.py} +1 -1
  24. django_spire/ai/chat/views/json_views.py +4 -3
  25. django_spire/ai/chat/views/message_request_views.py +32 -14
  26. django_spire/ai/chat/views/message_response_views.py +14 -11
  27. django_spire/ai/chat/views/message_views.py +7 -1
  28. django_spire/ai/chat/views/page_views.py +2 -2
  29. django_spire/ai/chat/views/{template_views.py → template/template_views.py} +1 -7
  30. django_spire/ai/context/__init__.py +0 -0
  31. django_spire/ai/context/admin.py +15 -0
  32. django_spire/ai/context/apps.py +16 -0
  33. django_spire/ai/context/choices.py +11 -0
  34. django_spire/ai/context/intelligence/__init__.py +0 -0
  35. django_spire/ai/context/intelligence/prompts/__init__.py +0 -0
  36. django_spire/ai/context/intelligence/prompts/organization_prompts.py +19 -0
  37. django_spire/ai/context/migrations/0001_initial.py +67 -0
  38. django_spire/ai/context/migrations/__init__.py +0 -0
  39. django_spire/ai/context/models.py +67 -0
  40. django_spire/ai/context/querysets.py +15 -0
  41. django_spire/ai/context/seeding/__init__.py +0 -0
  42. django_spire/ai/context/seeding/seed.py +24 -0
  43. django_spire/ai/prompt/system/bots.py +3 -5
  44. django_spire/ai/prompt/tuning/bots.py +5 -10
  45. django_spire/ai/sms/admin.py +2 -0
  46. django_spire/ai/sms/apps.py +2 -7
  47. django_spire/ai/sms/decorators.py +2 -2
  48. django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +18 -18
  49. django_spire/ai/sms/tests/test_webhook.py +5 -4
  50. django_spire/ai/sms/urls.py +3 -0
  51. django_spire/ai/sms/views.py +3 -5
  52. django_spire/ai/urls.py +2 -0
  53. django_spire/auth/templates/django_spire/auth/form/login_form.html +25 -0
  54. django_spire/auth/templates/django_spire/auth/page/auth_page.html +2 -3
  55. django_spire/auth/templates/django_spire/auth/page/login_page.html +1 -25
  56. django_spire/conf.py +1 -3
  57. django_spire/consts.py +1 -7
  58. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +3 -2
  59. django_spire/contrib/utils.py +2 -0
  60. django_spire/core/management/commands/spire_startapp_pkg/__init__.py +4 -20
  61. django_spire/core/management/commands/spire_startapp_pkg/builder.py +2 -2
  62. django_spire/core/management/commands/spire_startapp_pkg/config.py +2 -2
  63. django_spire/core/management/commands/spire_startapp_pkg/filesystem.py +1 -19
  64. django_spire/core/management/commands/spire_startapp_pkg/generator.py +8 -8
  65. django_spire/core/management/commands/spire_startapp_pkg/processor.py +3 -3
  66. django_spire/core/management/commands/spire_startapp_pkg/registry.py +0 -16
  67. django_spire/core/management/commands/spire_startapp_pkg/reporter.py +1 -21
  68. django_spire/core/management/commands/spire_startapp_pkg/resolver.py +0 -15
  69. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template +2 -2
  70. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template +2 -2
  71. django_spire/core/management/commands/spire_startapp_pkg/validator.py +9 -9
  72. django_spire/core/static/django_spire/css/app-button.css +11 -1
  73. django_spire/core/static/django_spire/css/app-import.css +2 -0
  74. django_spire/core/static/django_spire/css/app-layout.css +15 -0
  75. django_spire/core/static/django_spire/css/app-navigation.css +24 -2
  76. django_spire/core/static/django_spire/css/app-page.css +8 -0
  77. django_spire/core/static/django_spire/css/app-side-panel.css +102 -0
  78. django_spire/core/static/django_spire/css/app-text.css +19 -1
  79. django_spire/core/static/django_spire/css/bootstrap-extension.css +128 -7
  80. django_spire/core/static/django_spire/css/bootstrap-override.css +161 -111
  81. django_spire/core/static/django_spire/css/themes/ayu/app-dark.css +0 -4
  82. django_spire/core/static/django_spire/css/themes/ayu/app-light.css +0 -4
  83. django_spire/core/static/django_spire/css/themes/catppuccin/app-dark.css +0 -4
  84. django_spire/core/static/django_spire/css/themes/catppuccin/app-light.css +0 -4
  85. django_spire/core/static/django_spire/css/themes/default/app-dark.css +0 -4
  86. django_spire/core/static/django_spire/css/themes/default/app-light.css +0 -4
  87. django_spire/core/static/django_spire/css/themes/dracula/app-dark.css +0 -4
  88. django_spire/core/static/django_spire/css/themes/dracula/app-light.css +0 -4
  89. django_spire/core/static/django_spire/css/themes/gruvbox/app-dark.css +0 -4
  90. django_spire/core/static/django_spire/css/themes/gruvbox/app-light.css +0 -4
  91. django_spire/core/static/django_spire/css/themes/material/app-dark.css +0 -4
  92. django_spire/core/static/django_spire/css/themes/material/app-light.css +0 -4
  93. django_spire/core/static/django_spire/css/themes/nord/app-dark.css +0 -4
  94. django_spire/core/static/django_spire/css/themes/nord/app-light.css +0 -4
  95. django_spire/core/static/django_spire/css/themes/oceanic-next/app-dark.css +0 -4
  96. django_spire/core/static/django_spire/css/themes/oceanic-next/app-light.css +0 -4
  97. django_spire/core/static/django_spire/css/themes/one-dark/app-dark.css +0 -4
  98. django_spire/core/static/django_spire/css/themes/one-dark/app-light.css +0 -4
  99. django_spire/core/static/django_spire/css/themes/palenight/app-dark.css +0 -4
  100. django_spire/core/static/django_spire/css/themes/palenight/app-light.css +0 -4
  101. django_spire/core/static/django_spire/css/themes/rose-pine/app-dark.css +0 -4
  102. django_spire/core/static/django_spire/css/themes/rose-pine/app-light.css +0 -4
  103. django_spire/core/static/django_spire/css/themes/synthwave/app-dark.css +0 -4
  104. django_spire/core/static/django_spire/css/themes/synthwave/app-light.css +0 -4
  105. django_spire/core/static/django_spire/css/themes/tokyo-night/app-dark.css +0 -4
  106. django_spire/core/static/django_spire/css/themes/tokyo-night/app-light.css +0 -4
  107. django_spire/core/static/django_spire/font/Poppins-Black.ttf +0 -0
  108. django_spire/core/static/django_spire/font/Poppins-BlackItalic.ttf +0 -0
  109. django_spire/core/static/django_spire/font/Poppins-Bold.ttf +0 -0
  110. django_spire/core/static/django_spire/font/Poppins-BoldItalic.ttf +0 -0
  111. django_spire/core/static/django_spire/font/Poppins-ExtraBold.ttf +0 -0
  112. django_spire/core/static/django_spire/font/Poppins-ExtraBoldItalic.ttf +0 -0
  113. django_spire/core/static/django_spire/font/Poppins-ExtraLight.ttf +0 -0
  114. django_spire/core/static/django_spire/font/Poppins-ExtraLightItalic.ttf +0 -0
  115. django_spire/core/static/django_spire/font/Poppins-Italic.ttf +0 -0
  116. django_spire/core/static/django_spire/font/Poppins-Light.ttf +0 -0
  117. django_spire/core/static/django_spire/font/Poppins-LightItalic.ttf +0 -0
  118. django_spire/core/static/django_spire/font/Poppins-Medium.ttf +0 -0
  119. django_spire/core/static/django_spire/font/Poppins-MediumItalic.ttf +0 -0
  120. django_spire/core/static/django_spire/font/Poppins-SemiBold.ttf +0 -0
  121. django_spire/core/static/django_spire/font/Poppins-SemiBoldItalic.ttf +0 -0
  122. django_spire/core/static/django_spire/font/Poppins-Thin.ttf +0 -0
  123. django_spire/core/static/django_spire/font/Poppins-ThinItalic.ttf +0 -0
  124. django_spire/core/static/django_spire/js/ui.js +12 -0
  125. django_spire/core/templates/django_spire/button/base_button.html +1 -1
  126. django_spire/core/templates/django_spire/button/primary_dark_outlined_button.html +3 -0
  127. django_spire/core/templates/django_spire/dropdown/element/dropdown_link_element.html +12 -8
  128. django_spire/core/templates/django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html +2 -1
  129. django_spire/core/templates/django_spire/dropdown/ellipsis_dropdown.html +2 -1
  130. django_spire/core/templates/django_spire/navigation/accordion/nav_accordion.html +1 -1
  131. django_spire/core/templates/django_spire/navigation/elements/nav_link.html +1 -1
  132. django_spire/core/templates/django_spire/navigation/elements/nav_title_divider.html +1 -1
  133. django_spire/core/templates/django_spire/navigation/side_navigation.html +52 -8
  134. django_spire/core/templates/django_spire/navigation/top_navigation.html +8 -2
  135. django_spire/core/templates/django_spire/page/full_page.html +223 -11
  136. django_spire/core/utils.py +26 -2
  137. django_spire/knowledge/collection/models.py +24 -0
  138. django_spire/knowledge/collection/services/transformation_service.py +2 -1
  139. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +44 -42
  140. django_spire/knowledge/collection/urls/form_urls.py +1 -0
  141. django_spire/knowledge/collection/urls/page_urls.py +1 -0
  142. django_spire/knowledge/collection/views/form_views.py +13 -2
  143. django_spire/knowledge/collection/views/page_views.py +36 -4
  144. django_spire/knowledge/entry/models.py +32 -0
  145. django_spire/knowledge/entry/services/transformation_services.py +10 -10
  146. django_spire/knowledge/entry/version/block/data/data.py +2 -1
  147. django_spire/knowledge/entry/version/block/data/heading_data.py +2 -2
  148. django_spire/knowledge/entry/version/block/data/list/data.py +3 -4
  149. django_spire/knowledge/entry/version/block/data/maps.py +3 -5
  150. django_spire/knowledge/entry/version/block/data/text_data.py +2 -2
  151. django_spire/knowledge/entry/version/block/models.py +10 -7
  152. django_spire/knowledge/entry/version/block/services/factory_service.py +11 -10
  153. django_spire/knowledge/entry/version/block/tests/factories.py +6 -5
  154. django_spire/knowledge/entry/version/converters/docx_converter.py +1 -1
  155. django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +14 -12
  156. django_spire/knowledge/entry/version/seeding/seeder.py +3 -3
  157. django_spire/knowledge/entry/version/services/processor_service.py +36 -10
  158. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +1 -1
  159. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +1 -1
  160. django_spire/knowledge/entry/version/urls/page_urls.py +1 -1
  161. django_spire/knowledge/entry/version/views/json_views.py +5 -3
  162. django_spire/knowledge/entry/version/views/page_views.py +11 -12
  163. django_spire/knowledge/entry/version/views/redirect_views.py +1 -1
  164. django_spire/knowledge/entry/views/form_views.py +1 -1
  165. django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +33 -11
  166. django_spire/knowledge/intelligence/decoders/__init__.py +0 -0
  167. django_spire/knowledge/intelligence/{maps/collection_map.py → decoders/collection_decoder.py} +5 -5
  168. django_spire/knowledge/intelligence/{maps/entry_map.py → decoders/entry_decoder.py} +3 -3
  169. django_spire/knowledge/intelligence/intel/collection_intel.py +1 -0
  170. django_spire/knowledge/intelligence/intel/entry_intel.py +5 -2
  171. django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
  172. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +43 -51
  173. django_spire/knowledge/migrations/0007_alter_collection_options.py +17 -0
  174. django_spire/knowledge/static/django_spire/knowledge/entry/version/js/editor.js +24 -5
  175. django_spire/knowledge/templates/django_spire/knowledge/collection/card/top_level_list_card.html +21 -0
  176. django_spire/knowledge/templates/django_spire/knowledge/collection/component/x_collection_navigation.html +15 -6
  177. django_spire/knowledge/templates/django_spire/knowledge/collection/element/ellipsis_dropdown.html +22 -0
  178. django_spire/knowledge/templates/django_spire/knowledge/collection/form/form.html +21 -9
  179. django_spire/knowledge/templates/django_spire/knowledge/collection/item/collection_item.html +19 -0
  180. django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html +49 -0
  181. django_spire/knowledge/templates/django_spire/knowledge/collection/page/form_page.html +1 -1
  182. django_spire/knowledge/templates/django_spire/knowledge/container/container.html +33 -0
  183. django_spire/knowledge/templates/django_spire/knowledge/entry/file/page/list_page.html +1 -1
  184. django_spire/knowledge/templates/django_spire/knowledge/entry/item/list_item.html +2 -2
  185. django_spire/knowledge/templates/django_spire/knowledge/entry/page/form_page.html +1 -1
  186. django_spire/knowledge/templates/django_spire/knowledge/entry/page/import_form_page.html +1 -1
  187. django_spire/knowledge/templates/django_spire/knowledge/entry/version/container/detail_container.html +51 -45
  188. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +54 -0
  189. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/form_page.html +1 -1
  190. django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +18 -1
  191. django_spire/knowledge/templates/django_spire/knowledge/page/full_page.html +37 -0
  192. django_spire/knowledge/templates/django_spire/knowledge/page/home_page.html +2 -2
  193. django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/collection_sub_navigation_item.html +29 -0
  194. django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/entry_sub_navigation_item.html +20 -0
  195. django_spire/knowledge/templates/django_spire/knowledge/{navigation/content/navigation_content.html → sub_navigation/widget/collection_entry_sub_navigation_widget.html} +42 -19
  196. django_spire/knowledge/views/page_views.py +4 -4
  197. django_spire/settings.py +3 -3
  198. django_spire/theme/templates/django_spire/theme/card/badges_preview_card.html +0 -1
  199. django_spire/theme/templates/django_spire/theme/card/base_preview_card.html +1 -0
  200. django_spire/theme/templates/django_spire/theme/card/typography_preview_card.html +1 -1
  201. django_spire/theme/templates/django_spire/theme/example/form/example_form.html +2 -0
  202. django_spire/theme/templates/django_spire/theme/example/form/example_form_card.html +5 -0
  203. django_spire/theme/templates/django_spire/theme/page/badges_page.html +7 -9
  204. django_spire/theme/templates/django_spire/theme/page/borders_page.html +6 -9
  205. django_spire/theme/templates/django_spire/theme/page/buttons_page.html +6 -9
  206. django_spire/theme/templates/django_spire/theme/page/colors_page.html +6 -6
  207. django_spire/theme/templates/django_spire/theme/page/dashboard_page.html +57 -0
  208. django_spire/theme/templates/django_spire/theme/page/django_glue_page.html +7 -9
  209. django_spire/theme/templates/django_spire/theme/page/theme_page.html +24 -0
  210. django_spire/theme/templates/django_spire/theme/page/typography_page.html +6 -9
  211. django_spire/theme/templates/django_spire/theme/section/badge_section_card.html +5 -0
  212. django_spire/theme/templates/django_spire/theme/section/border_section_card.html +5 -0
  213. django_spire/theme/templates/django_spire/theme/section/button_section.html +1 -0
  214. django_spire/theme/templates/django_spire/theme/section/button_section_card.html +5 -0
  215. django_spire/theme/templates/django_spire/theme/section/color_section.html +10 -10
  216. django_spire/theme/templates/django_spire/theme/section/color_section_card.html +5 -0
  217. django_spire/theme/templates/django_spire/theme/section/typography_section.html +41 -1
  218. django_spire/theme/templates/django_spire/theme/section/typography_section_card.html +5 -0
  219. {django_spire-0.18.3.dist-info → django_spire-0.19.1.dist-info}/METADATA +3 -3
  220. {django_spire-0.18.3.dist-info → django_spire-0.19.1.dist-info}/RECORD +229 -190
  221. django_spire/ai/chat/intelligence/bots/chat_bot.py +0 -8
  222. django_spire/ai/chat/intelligence/maps/intent_llm_map.py +0 -10
  223. django_spire/ai/chat/templates/django_spire/ai/chat/page/home_page.html +0 -9
  224. django_spire/ai/chat/templates/django_spire/ai/chat/widget/chat_widget.html +0 -64
  225. django_spire/ai/chat/templates/django_spire/ai/chat/widget/search_chat_results_widget.html +0 -25
  226. django_spire/ai/chat/templates/django_spire/ai/chat/widget/search_chat_widget.html +0 -39
  227. django_spire/ai/chat/templates/django_spire/ai/chat/widget/select_chat_widget.html +0 -24
  228. django_spire/ai/chat/tools.py +0 -57
  229. django_spire/ai/sms/tools.py +0 -57
  230. django_spire/core/static/django_spire/font/Karla-Bold.ttf +0 -0
  231. django_spire/core/static/django_spire/font/Merriweather.ttf +0 -0
  232. django_spire/knowledge/context_processors.py +0 -18
  233. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/detail_page.html +0 -25
  234. django_spire/knowledge/templates/django_spire/knowledge/navigation/card/navigation_card.html +0 -9
  235. django_spire/knowledge/templates/django_spire/knowledge/navigation/item/collection/collection_item.html +0 -29
  236. django_spire/knowledge/templates/django_spire/knowledge/navigation/item/entry/entry_item.html +0 -18
  237. django_spire/knowledge/templates/django_spire/knowledge/navigation/page/full_page.html +0 -90
  238. /django_spire/ai/chat/intelligence/{bots → decoders}/__init__.py +0 -0
  239. /django_spire/ai/chat/{intelligence/maps → urls/template}/__init__.py +0 -0
  240. /django_spire/{knowledge/intelligence/maps → ai/chat/views/template}/__init__.py +0 -0
  241. /django_spire/knowledge/entry/version/{constants.py → consts.py} +0 -0
  242. /django_spire/knowledge/templates/django_spire/knowledge/{navigation/item/collection/dropdown/navigation_ellipsis_dropdown.html → sub_navigation/element/collection_sub_navigation_ellipsis_dropdown.html} +0 -0
  243. /django_spire/knowledge/templates/django_spire/knowledge/{navigation/item/entry/dropdown/navigation_ellipsis_dropdown.html → sub_navigation/element/entry_sub_navigation_ellipsis_dropdown.html} +0 -0
  244. {django_spire-0.18.3.dist-info → django_spire-0.19.1.dist-info}/WHEEL +0 -0
  245. {django_spire-0.18.3.dist-info → django_spire-0.19.1.dist-info}/licenses/LICENSE.md +0 -0
  246. {django_spire-0.18.3.dist-info → django_spire-0.19.1.dist-info}/top_level.txt +0 -0
django_spire/ai/admin.py CHANGED
@@ -35,6 +35,7 @@ class AiUsageAdmin(AiUsageAdminMixin):
35
35
  was_successful: bool | None = None,
36
36
  ) -> str:
37
37
  was_successful_filter = '' if was_successful is None else f'&was_successful__exact={int(was_successful)}'
38
+
38
39
  url = (
39
40
  reverse("admin:django_spire_ai_aiinteraction_changelist")
40
41
  + "?"
@@ -1,9 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.apps import AppConfig
4
- from django.conf import settings
5
-
6
- from django_spire.consts import AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME
7
4
  from django_spire.utils import check_required_apps
8
5
 
9
6
 
@@ -20,11 +17,7 @@ class AiChatConfig(AppConfig):
20
17
  },
21
18
  )
22
19
 
23
- REQUIRED_APPS = ('django_spire_ai',)
20
+ REQUIRED_APPS = ('django_spire_ai', 'django_spire_ai_context')
24
21
 
25
22
  def ready(self) -> None:
26
- if not isinstance(getattr(settings, AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME), str):
27
- message = f'"{AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME}" must be set in the django settings when using "{self.label}".'
28
- raise TypeError(message)
29
-
30
23
  check_required_apps(self.label)
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable, TYPE_CHECKING
4
+
5
+ from dandy import Decoder
6
+
7
+ from django_spire.auth.controller.controller import AppAuthController
8
+ from django_spire.knowledge.intelligence.workflows.knowledge_workflow import knowledge_search_workflow
9
+
10
+ if TYPE_CHECKING:
11
+ from django.core.handlers.wsgi import WSGIRequest
12
+
13
+
14
+ def generate_intent_decoder(
15
+ request: WSGIRequest,
16
+ default_callable: Callable | None = None
17
+ ) -> Decoder:
18
+ intent_dict = {}
19
+
20
+ if AppAuthController(app_name='knowledge', request=request).can_view():
21
+ intent_dict['The user is looking for information or knowledge on something.'] = knowledge_search_workflow
22
+
23
+ if default_callable is not None:
24
+ intent_dict['None of the above choices match the user\'s intent'] = default_callable
25
+
26
+ return Decoder(
27
+ mapping_keys_description='Intent of the User\'s Request',
28
+ mapping=intent_dict
29
+ )
@@ -2,18 +2,24 @@ from __future__ import annotations
2
2
 
3
3
  from dandy import Prompt
4
4
 
5
- from django_spire.conf import settings
5
+ from django_spire.ai.context.intelligence.prompts.organization_prompts import (
6
+ organization_info_prompt
7
+ )
6
8
 
7
9
 
8
- def organization_prompt() -> Prompt:
10
+ def chat_system_prompt() -> Prompt:
9
11
  return (
10
12
  Prompt()
11
- .text(f'You are a chat assistant for a company called "{settings.ORGANIZATION_NAME}".')
13
+ .text(
14
+ 'You are a chat assistant, please use the organization information below for answering questions.'
15
+ )
12
16
  .line_break()
13
- .text(f'Organization Description: "{settings.ORGANIZATION_DESCRIPTION}"')
17
+ .prompt(organization_info_prompt())
14
18
  .line_break()
15
19
  .text('Make sure to follow the rules below')
16
- .list([
17
- 'Do not talk about being an AI at all to maintain the illusion of being a human assistant.',
18
- ])
20
+ .list(
21
+ [
22
+ 'Do not talk about being an AI at all to maintain the illusion of being a human assistant.',
23
+ ]
24
+ )
19
25
  )
@@ -1,71 +1,69 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Callable
4
4
 
5
- from dandy import Prompt, Bot
5
+ from dandy import Bot
6
6
  from dandy.recorder import recorder_to_html_file
7
7
 
8
- from django_spire.ai.chat.intelligence.bots.chat_bot import ChatBot
9
- from django_spire.ai.chat.intelligence.maps.intent_llm_map import IntentDecoder
10
- from django_spire.ai.chat.intelligence.prompts import organization_prompt
11
- from django_spire.ai.chat.message_intel import DefaultMessageIntel
12
- from django_spire.auth.controller.controller import AppAuthController
8
+ from django_spire.ai.chat.intelligence.decoders.tools import generate_intent_decoder
9
+ from django_spire.ai.chat.message_intel import BaseMessageIntel, DefaultMessageIntel
10
+ from django_spire.ai.decorators import log_ai_interaction_from_recorder
13
11
 
14
12
  if TYPE_CHECKING:
15
13
  from dandy.llm.request.message import MessageHistory
16
14
  from django.core.handlers.wsgi import WSGIRequest
17
15
 
18
- from django_spire.ai.chat.message_intel import BaseMessageIntel
19
16
 
17
+ def SpireChatWorkflow(
18
+ request: WSGIRequest,
19
+ user_input: str,
20
+ message_history: MessageHistory | None = None
21
+ ) -> BaseMessageIntel:
22
+ return chat_workflow(
23
+ request=request,
24
+ user_input=user_input,
25
+ message_history=message_history
26
+ )
27
+
28
+
29
+ def default_chat_response(
30
+ request: WSGIRequest,
31
+ user_input: str,
32
+ message_history: MessageHistory | None = None
33
+ ) -> BaseMessageIntel:
34
+ bot = Bot()
35
+ return bot.llm.prompt_to_intel(
36
+ prompt=user_input,
37
+ intel_class=DefaultMessageIntel,
38
+ message_history=message_history,
39
+ )
40
+
41
+
42
+ @recorder_to_html_file('spire_ai_chat_workflow')
43
+ def chat_workflow(
44
+ request: WSGIRequest,
45
+ user_input: str,
46
+ message_history: MessageHistory | None = None
47
+ ) -> BaseMessageIntel:
48
+ intent_decoder = generate_intent_decoder(
49
+ request=request,
50
+ default_callable=default_chat_response,
51
+ )
52
+
53
+ intent_process = intent_decoder.process(user_input, max_return_values=1)[0]
54
+
55
+ @log_ai_interaction_from_recorder(request.user)
56
+ def run_workflow_process(callable_: Callable) -> BaseMessageIntel | None:
57
+ return callable_(
58
+ request=request,
59
+ user_input=user_input,
60
+ message_history=message_history,
61
+ )
20
62
 
21
- class SpireChatWorkflow:
22
- @staticmethod
23
- def _generate_intent_decoder(request: WSGIRequest) -> IntentDecoder:
24
- from django_spire.knowledge.intelligence.workflows.knowledge_workflow import KnowledgeWorkflow
25
-
26
- intent_dict = {}
27
-
28
- if AppAuthController(app_name='knowledge', request=request).can_view():
29
- intent_message = (
30
- 'The user is looking for information or knowledge on something.'
31
- )
32
-
33
- intent_dict[intent_message] = KnowledgeWorkflow
34
-
35
- intent_dict['None of the above choices match the user\'s intent'] = None
36
-
37
- decoder = IntentDecoder()
38
- decoder.mapping = intent_dict
39
- return decoder
40
-
41
- @staticmethod
42
- @recorder_to_html_file('spire_chat_workflow')
43
- def process(
44
- request: WSGIRequest,
45
- user_input: str,
46
- message_history: MessageHistory | None = None
47
- ) -> BaseMessageIntel:
48
- intent_decoder = SpireChatWorkflow._generate_intent_decoder(request)
49
- intents = intent_decoder.process(user_input, max_return_values=1)
50
-
51
- chat_bot = ChatBot()
63
+ message_intel = run_workflow_process(intent_process)
52
64
 
53
- if intents[0] is None:
54
- return chat_bot.llm.prompt_to_intel(
55
- prompt=user_input,
56
- intel_class=DefaultMessageIntel,
57
- message_history=message_history,
58
- postfix_system_prompt=organization_prompt()
59
- )
65
+ if not isinstance(message_intel, BaseMessageIntel):
66
+ message = f'{intent_process.__qualname__} must return an instance of a {BaseMessageIntel.__name__} sub class.'
67
+ raise TypeError(message)
60
68
 
61
- return chat_bot.llm.prompt_to_intel(
62
- prompt=(
63
- Prompt()
64
- .text(f'User Input: {user_input}')
65
- .line_break()
66
- .text(intents[0].process(user_input))
67
- ),
68
- intel_class=DefaultMessageIntel,
69
- message_history=message_history,
70
- postfix_system_prompt=organization_prompt()
71
- )
69
+ return message_intel
@@ -143,7 +143,8 @@ class ChatMessage(HistoryModelMixin):
143
143
  return MessageResponse(
144
144
  type=MessageResponseType(self.response_type),
145
145
  sender=self.sender,
146
- message_intel=self.intel
146
+ message_intel=self.intel,
147
+ message_timestamp=self.created_datetime.strftime('%b %d, %Y at %I:%M %p')
147
148
  )
148
149
 
149
150
  class Meta:
@@ -7,9 +7,7 @@ from django_spire.history.querysets import HistoryQuerySet
7
7
 
8
8
  class ChatQuerySet(HistoryQuerySet):
9
9
  def by_user(self, user):
10
- return self.filter(
11
- user=user,
12
- )
10
+ return self.filter(user=user)
13
11
 
14
12
  def get_empty_or_create(self, user):
15
13
  try:
@@ -18,12 +18,14 @@ class MessageResponse:
18
18
  type: MessageResponseType
19
19
  sender: str
20
20
  message_intel: BaseMessageIntel
21
+ message_timestamp: str | None = None
21
22
  synthesis_speech: bool = False
22
23
 
23
24
  def _render_template_to_html_string(self, template: str, context_data: dict[str, Any] | None = None) -> str:
24
25
  return render_to_string(
25
26
  template_name=template,
26
27
  context={
28
+ 'message_timestamp': self.message_timestamp,
27
29
  'sender': self.sender,
28
30
  'message_intel': self.message_intel,
29
31
  'synthesis_speech': self.synthesis_speech,
@@ -63,6 +65,9 @@ class MessageResponseGroup:
63
65
  def render_to_html_string(self, context_data: dict[str, Any] | None = None) -> str:
64
66
  html_string = ''
65
67
 
68
+ context_data = context_data or {}
69
+ context_data['is_loading'] = context_data.get('is_loading', False)
70
+
66
71
  for message_response in self.message_responses:
67
72
  html_string += message_response.render_to_html_string(context_data)
68
73
 
@@ -6,8 +6,8 @@
6
6
 
7
7
  {% block card_content %}
8
8
  <div class="row">
9
- <div class="col-12 vh-minus-nav">
10
- {% include 'django_spire/ai/chat/widget/chat_widget.html' %}
9
+ <div class="col-12 vh-minus-top-nav">
10
+ {% include 'django_spire/ai/chat/widget/selection_widget.html' %}
11
11
  </div>
12
12
  </div>
13
13
  {% endblock %}
@@ -11,7 +11,7 @@
11
11
  {% endblock %}
12
12
 
13
13
  {% block dropdown_content %}
14
- {% include 'django_spire/dropdown/element/dropdown_link_element.html' with x_click="enable_rename_chat()" link_text='Rename' link_css='text-start' %}
14
+ {% include 'django_spire/dropdown/element/dropdown_link_element.html' with x_click="enable_rename_chat(); toggle_dropdown()" link_text='Rename' link_css='text-start' %}
15
15
 
16
16
  {% url 'django_spire:ai:chat:template:confirm_delete' pk=recent_chat.id as delete_url %}
17
17
  {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with view_url=delete_url link_text='Delete' link_css='text-app-danger text-start' %}
@@ -1,84 +1,122 @@
1
1
  <div
2
- class="row mb-2"
2
+ class="mb-1"
3
3
  x-data="{
4
4
  can_rename_chat: false,
5
5
  chat_name: '{{ recent_chat.name|escapejs }}',
6
+ is_active: false,
6
7
  new_chat_name: '{{ recent_chat.name|escapejs }}',
7
8
  show_recent_chat: true,
8
9
 
10
+ cancel_rename_chat() {
11
+ this.new_chat_name = this.chat_name
12
+ this.can_rename_chat = false
13
+ },
14
+
15
+ confirm_rename_chat() {
16
+ this.rename_chat()
17
+ },
18
+
9
19
  disable_rename_chat() {
10
20
  this.can_rename_chat = false
11
21
  },
22
+
12
23
  enable_rename_chat() {
13
24
  this.can_rename_chat = true
25
+
26
+ $nextTick(() => {
27
+ let input = this.$el.querySelector('input')
28
+ if (input) input.focus()
29
+ })
14
30
  },
31
+
15
32
  async rename_chat() {
33
+ if (this.new_chat_name.trim() === '') {
34
+ this.new_chat_name = this.chat_name
35
+ this.can_rename_chat = false
36
+ return
37
+ }
38
+
16
39
  let response = await django_glue_fetch(
17
40
  '{% url "django_spire:ai:chat:json:rename" pk=recent_chat.pk %}',
18
41
  {payload: {new_name: this.new_chat_name}}
19
42
  )
20
43
 
21
44
  let type = response.type
45
+
22
46
  if (type === 'success') {
23
47
  this.chat_name = this.new_chat_name
24
48
  } else {
25
49
  $dispatch('notify', {'type': type, 'message': response.message})
50
+ this.new_chat_name = this.chat_name
26
51
  }
27
52
 
28
53
  this.can_rename_chat = false
29
54
  }
30
55
  }"
31
56
  x-show="show_recent_chat"
57
+ @chat-activated.window="is_active = ($event.detail.chat_id === {{ recent_chat.id }})"
58
+ @click.outside="if (can_rename_chat) cancel_rename_chat()"
32
59
  @deleted_chat.window="if (show_recent_chat) show_recent_chat = $event.detail !== {{ recent_chat.id }}"
33
60
  >
34
- <div class="col-12">
35
- <div class="d-flex flex-wrap justify-content-between btn shadow-sm btn-sm w-100 text-muted">
36
- <div
37
- x-show="!can_rename_chat"
38
- class="col cursor-pointer px-0 text-start"
39
- @click="() => {
40
- load_chat({{ recent_chat.id }})
41
- close_chat_select()
61
+ <div
62
+ :class="is_active ? 'bg-app-primary-soft' : ''"
63
+ class="d-flex align-items-center justify-content-between px-2 py-1 rounded"
64
+ data-chat-id="{{ recent_chat.id }}"
65
+ style="transition: background-color 0.15s ease;"
66
+ >
67
+ <div class="d-flex align-items-center flex-grow-1" style="min-width: 0; width: 0;">
68
+ <i
69
+ :class="{
70
+ 'bi-chat-dots-fill': !can_rename_chat,
71
+ 'bi-chat-dots': can_rename_chat,
72
+ 'text-app-primary': is_active || can_rename_chat,
73
+ 'text-muted': !is_active && !can_rename_chat
42
74
  }"
43
- >
44
- <i class="bi bi-chat-dots-fill"></i>
45
- <span x-text="chat_name.length > 24 ? chat_name.slice(0, 24) + '...' : chat_name"></span>
46
- </div>
47
- <div
48
- x-show="can_rename_chat"
49
- class="col px-0 text-start"
50
- >
51
- <div class="d-flex flex-wrap align-items-center">
52
- <i class="col-auto bi bi-chat-dots-fill"></i>
75
+ class="bi me-2"
76
+ style="flex-shrink: 0;"
77
+ ></i>
78
+
79
+ <template x-if="!can_rename_chat">
80
+ <div @click="$dispatch('load-chat', {chat_id: {{ recent_chat.id }}})" class="cursor-pointer flex-grow-1 text-truncate">
81
+ <span class="px-2 fs-7" x-text="chat_name"></span>
82
+ </div>
83
+ </template>
84
+
85
+ <template x-if="can_rename_chat">
86
+ <div class="d-flex align-items-center position-relative w-100 py-0 px-1" style="flex: 1 1 0; min-width: 0;">
53
87
  <input
54
- x-model="new_chat_name"
55
- class="col form-control d-inline p-0 mx-1 fs-7"
56
- type="text"
57
- style="max-width: 150px;"
58
- @keydown.enter="rename_chat()"
59
- @keydown.esc="disable_rename_chat()"
60
88
  @focus="$el.select()"
89
+ @keydown.enter="confirm_rename_chat()"
90
+ @keydown.esc="cancel_rename_chat()"
91
+ class="form-control ps-1 pe-5 py-0 fs-7"
92
+ type="text"
93
+ x-init="$el.focus()"
94
+ x-model="new_chat_name"
61
95
  />
62
- <i
63
- class="col-auto bi bi-check fs-5 btn-hover"
64
- @click="rename_chat()"
65
- title="Confirm"
66
- ></i>
67
- <i
68
- class="col-auto bi bi-x fs-5 btn-hover"
69
- @click="disable_rename_chat()"
70
- title="Cancel"
71
- ></i>
72
- </div>
73
- </div>
74
- {% if not recent_chat.is_empty %}
75
- <div
76
- x-show="!can_rename_chat"
77
- class="col-auto px-0 text-end"
78
- >
79
- {% include 'django_spire/ai/chat/dropdown/ellipsis_dropdown.html' %}
96
+ <div class="d-flex align-items-center position-absolute g-2" style="pointer-events: none; right: 8px;">
97
+ <i
98
+ @click="confirm_rename_chat()"
99
+ @mouseenter="$el.style.opacity = '0.75'"
100
+ @mouseleave="$el.style.opacity = '1'"
101
+ class="bi bi-check cursor-pointer text-success"
102
+ style="pointer-events: auto; transition: opacity 0.15s ease;"
103
+ title="Confirm"
104
+ ></i>
105
+ <i
106
+ @click="cancel_rename_chat()"
107
+ @mouseenter="$el.style.opacity = '0.75'"
108
+ @mouseleave="$el.style.opacity = '1'"
109
+ class="bi bi-x cursor-pointer text-danger pe-1"
110
+ style="pointer-events: auto; transition: opacity 0.15s ease;"
111
+ title="Cancel"
112
+ ></i>
113
+ </div>
80
114
  </div>
81
- {% endif %}
115
+ </template>
116
+ </div>
117
+
118
+ <div class="ms-2" style="flex-shrink: 0; visibility: hidden;" :style="can_rename_chat ? '' : 'visibility: visible;'">
119
+ {% include 'django_spire/ai/chat/dropdown/ellipsis_dropdown.html' %}
82
120
  </div>
83
121
  </div>
84
122
  </div>
@@ -1,11 +1,13 @@
1
1
  {% load spire_core_tags %}
2
2
 
3
3
  {% generate_id as response_message_intel_id %}
4
+
4
5
  <div
5
6
  x-data="{
6
7
  show_loading: false,
7
8
  show_reading: true,
8
9
  show_typing: false,
10
+
9
11
  init() {
10
12
  this.response_message.render_outer($refs.loading_response_message)
11
13
 
@@ -14,6 +16,7 @@
14
16
  setTimeout(() => {this.show_reading = false}, 4000)
15
17
  setTimeout(() => {this.show_typing = true}, 4000)
16
18
  },
19
+
17
20
  response_message: new ViewGlue(
18
21
  '{% url "django_spire:ai:chat:message:response:new" %}',
19
22
  {
@@ -27,18 +30,20 @@
27
30
  x-cloak
28
31
  >
29
32
  {{ message_intel.text|json_script:response_message_intel_id }}
33
+
30
34
  <div x-ref="loading_response_message" class="row mt-2 pb-3">
31
35
  <div class="col-auto align-self-center">
32
36
  <div class="row align-items-center">
33
37
  <div class="col-12 fs--1">
34
38
  {{ chat_workflow_name }}
35
39
  </div>
36
- <div class="col-12 ps-4 pt-2">
37
- <span class="spinner-border spinner-border-sm" role="status">
40
+
41
+ <div class="col-12 px-3 pt-2">
42
+ <span class="spinner-border spinner-border-sm text-muted" role="status">
38
43
  <span class="visually-hidden">Loading...</span>
39
44
  </span>
40
- <span x-show="show_reading">Reading...</span>
41
- <span x-show="show_typing">Typing...</span>
45
+ <span class="text-muted" x-show="show_reading">Reading...</span>
46
+ <span class="text-muted" x-show="show_typing">Typing...</span>
42
47
  </div>
43
48
  </div>
44
49
  </div>
@@ -2,50 +2,50 @@
2
2
 
3
3
  {% block message %}
4
4
  {% generate_id as message_intel_id %}
5
+
5
6
  <div
6
7
  class="col-auto mb-1 {{ message_class }}"
7
- @mouseover="show_message_menu = true"
8
8
  @mouseleave="show_message_menu = false"
9
+ @mouseover="show_message_menu = true"
10
+ style="max-width: 70%;"
9
11
  x-data="{
10
- show_message_menu: false,
11
12
  message_intel_str: '',
13
+ show_message_menu: false,
14
+
12
15
  init() {
13
16
  this.message_intel_str = JSON.parse(document.getElementById('{{ message_intel_id }}').textContent)
14
- {% if synthesis_speech %}this.start_speaking(this.message_intel_str){% endif %}
15
- $el.scrollIntoView({behavior: 'smooth'})
17
+ {% if synthesis_speech %}$dispatch('speak', {text: this.message_intel_str}){% endif %}
18
+ {% if not is_loading %}$el.scrollIntoView({behavior: 'smooth'}){% endif %}
16
19
  }
17
20
  }"
18
21
  >
19
- <div class="container-fluid">
20
- {{ message_intel.content_to_str|json_script:message_intel_id }}
21
- <div class="row">
22
- <div class="col fs--1 {{ sender_class }}">
23
- {% block message_sender %}
24
- {{ sender }}
25
- {% endblock %}
26
- </div>
27
- </div>
28
- <div class="row">
29
- <div class="col-12 px-3 py-2 rounded-3 shadow-sm {{ content_class|default:'border' }}">
30
- {% block message_content %}
31
- {{ message_intel.content_to_str|linebreaksbr }}
32
- {% endblock %}
33
- </div>
22
+ {{ message_intel.content_to_str|json_script:message_intel_id }}
23
+
24
+ <div class="px-1 text-muted {{ sender_class }}" style="font-size: 0.7rem;">
25
+ {% block message_sender %}
26
+ <span class="fw-semibold">{{ sender }}</span>
27
+ {% endblock %}
28
+
29
+ {% if message_timestamp %}
30
+ <span>- {{ message_timestamp }}</span>
31
+ {% endif %}
32
+ </div>
33
+
34
+ <div class="d-inline-block px-2 py-1 rounded-3 shadow-sm {{ content_class|default:'border' }}">
35
+ {% block message_content %}
36
+ {{ message_intel.content_to_str|linebreaksbr }}
37
+ {% endblock %}
38
+ </div>
39
+
40
+ <div
41
+ :class="show_message_menu ? 'opacity-100' : 'opacity-0'"
42
+ class="d-flex justify-content-end"
43
+ >
44
+ <div @click="$dispatch('speak', {text: message_intel_str})" class="py-1 px-2 cursor-pointer">
45
+ <i class="bi bi-soundwave"></i>
34
46
  </div>
35
- <div
36
- class="row justify-content-end"
37
- :class="show_message_menu ? 'opacity-100' : 'opacity-0'"
38
- >
39
- <div class="col-auto px-0">
40
- <div @click="start_speaking(message_intel_str)" class="py-1 px-2 cursor-pointer">
41
- <i class="bi bi-soundwave"></i>
42
- </div>
43
- </div>
44
- <div class="col-auto pe-0 ps-1">
45
- <div @click="send_chat({message_body: message_intel_str})" class="py-1 px-2 cursor-pointer">
46
- <i class="bi bi-repeat"></i>
47
- </div>
48
- </div>
47
+ <div @click="$dispatch('send-message', {message_body: message_intel_str})" class="py-1 px-2 cursor-pointer">
48
+ <i class="bi bi-repeat"></i>
49
49
  </div>
50
50
  </div>
51
51
  </div>
@@ -1,14 +1,11 @@
1
- <div
2
- class="row justify-content-end ps-4 ps-lg-5"
3
- x-data="{
1
+ <div class="row justify-content-end ps-4 ps-lg-5" data-message-chat-id="{{ chat_id }}" {% if not is_loading %}x-data="{
4
2
  show_message: false,
3
+
5
4
  init() {
6
5
  setTimeout(() => this.show_message = true, 500)
7
6
  setTimeout(() => $el.scrollIntoView({behavior: 'smooth'}), 550)
8
7
  }
9
- }"
10
- x-show="show_message"
11
- x-transition
12
- >
13
- {% include message_intel.template with sender_class="text-end" speech_synthesis_class='justify-content-end' content_class="bg-app-layer-three" %}
8
+ }" x-show="show_message" x-transition{% endif %}>
9
+
10
+ {% include message_intel.template with sender_class="text-end" speech_synthesis_class='justify-content-end' content_class="bg-app-primary-soft" %}
14
11
  </div>
@@ -1,14 +1,18 @@
1
1
  <div
2
- x-data="{
3
- show_message: false,
4
- init() {
5
- setTimeout(() => this.show_message = true, 300)
6
- setTimeout(() => $el.scrollIntoView({behavior: 'smooth'}), 350)
7
- }
8
- }"
9
2
  class="row justify-content-start pe-4 pe-lg-5"
10
- x-show="show_message"
11
- x-transition
3
+ data-message-chat-id="{{ chat_id }}"
4
+ {% if not is_loading %}
5
+ x-data="{
6
+ show_message: false,
7
+
8
+ init() {
9
+ setTimeout(() => this.show_message = true, 300)
10
+ setTimeout(() => $el.scrollIntoView({behavior: 'smooth'}), 350)
11
+ }
12
+ }"
13
+ x-show="show_message"
14
+ x-transition
15
+ {% endif %}
12
16
  >
13
- {% include message_intel.template with content_class="bg-app-alt-layer-three" %}
17
+ {% include message_intel.template with content_class="bg-app-warning-soft" %}
14
18
  </div>