django-spire 0.18.3__py3-none-any.whl → 0.19.0__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 (234) 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 +40 -52
  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 +8 -3
  13. django_spire/ai/chat/templates/django_spire/ai/chat/message/message.html +16 -7
  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 -23
  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/static/django_spire/css/app-button.css +11 -1
  61. django_spire/core/static/django_spire/css/app-import.css +2 -0
  62. django_spire/core/static/django_spire/css/app-layout.css +15 -0
  63. django_spire/core/static/django_spire/css/app-navigation.css +24 -2
  64. django_spire/core/static/django_spire/css/app-page.css +8 -0
  65. django_spire/core/static/django_spire/css/app-side-panel.css +102 -0
  66. django_spire/core/static/django_spire/css/app-text.css +19 -1
  67. django_spire/core/static/django_spire/css/bootstrap-extension.css +128 -7
  68. django_spire/core/static/django_spire/css/bootstrap-override.css +161 -111
  69. django_spire/core/static/django_spire/css/themes/ayu/app-dark.css +0 -4
  70. django_spire/core/static/django_spire/css/themes/ayu/app-light.css +0 -4
  71. django_spire/core/static/django_spire/css/themes/catppuccin/app-dark.css +0 -4
  72. django_spire/core/static/django_spire/css/themes/catppuccin/app-light.css +0 -4
  73. django_spire/core/static/django_spire/css/themes/default/app-dark.css +0 -4
  74. django_spire/core/static/django_spire/css/themes/default/app-light.css +0 -4
  75. django_spire/core/static/django_spire/css/themes/dracula/app-dark.css +0 -4
  76. django_spire/core/static/django_spire/css/themes/dracula/app-light.css +0 -4
  77. django_spire/core/static/django_spire/css/themes/gruvbox/app-dark.css +0 -4
  78. django_spire/core/static/django_spire/css/themes/gruvbox/app-light.css +0 -4
  79. django_spire/core/static/django_spire/css/themes/material/app-dark.css +0 -4
  80. django_spire/core/static/django_spire/css/themes/material/app-light.css +0 -4
  81. django_spire/core/static/django_spire/css/themes/nord/app-dark.css +0 -4
  82. django_spire/core/static/django_spire/css/themes/nord/app-light.css +0 -4
  83. django_spire/core/static/django_spire/css/themes/oceanic-next/app-dark.css +0 -4
  84. django_spire/core/static/django_spire/css/themes/oceanic-next/app-light.css +0 -4
  85. django_spire/core/static/django_spire/css/themes/one-dark/app-dark.css +0 -4
  86. django_spire/core/static/django_spire/css/themes/one-dark/app-light.css +0 -4
  87. django_spire/core/static/django_spire/css/themes/palenight/app-dark.css +0 -4
  88. django_spire/core/static/django_spire/css/themes/palenight/app-light.css +0 -4
  89. django_spire/core/static/django_spire/css/themes/rose-pine/app-dark.css +0 -4
  90. django_spire/core/static/django_spire/css/themes/rose-pine/app-light.css +0 -4
  91. django_spire/core/static/django_spire/css/themes/synthwave/app-dark.css +0 -4
  92. django_spire/core/static/django_spire/css/themes/synthwave/app-light.css +0 -4
  93. django_spire/core/static/django_spire/css/themes/tokyo-night/app-dark.css +0 -4
  94. django_spire/core/static/django_spire/css/themes/tokyo-night/app-light.css +0 -4
  95. django_spire/core/static/django_spire/font/Poppins-Black.ttf +0 -0
  96. django_spire/core/static/django_spire/font/Poppins-BlackItalic.ttf +0 -0
  97. django_spire/core/static/django_spire/font/Poppins-Bold.ttf +0 -0
  98. django_spire/core/static/django_spire/font/Poppins-BoldItalic.ttf +0 -0
  99. django_spire/core/static/django_spire/font/Poppins-ExtraBold.ttf +0 -0
  100. django_spire/core/static/django_spire/font/Poppins-ExtraBoldItalic.ttf +0 -0
  101. django_spire/core/static/django_spire/font/Poppins-ExtraLight.ttf +0 -0
  102. django_spire/core/static/django_spire/font/Poppins-ExtraLightItalic.ttf +0 -0
  103. django_spire/core/static/django_spire/font/Poppins-Italic.ttf +0 -0
  104. django_spire/core/static/django_spire/font/Poppins-Light.ttf +0 -0
  105. django_spire/core/static/django_spire/font/Poppins-LightItalic.ttf +0 -0
  106. django_spire/core/static/django_spire/font/Poppins-Medium.ttf +0 -0
  107. django_spire/core/static/django_spire/font/Poppins-MediumItalic.ttf +0 -0
  108. django_spire/core/static/django_spire/font/Poppins-SemiBold.ttf +0 -0
  109. django_spire/core/static/django_spire/font/Poppins-SemiBoldItalic.ttf +0 -0
  110. django_spire/core/static/django_spire/font/Poppins-Thin.ttf +0 -0
  111. django_spire/core/static/django_spire/font/Poppins-ThinItalic.ttf +0 -0
  112. django_spire/core/static/django_spire/js/ui.js +12 -0
  113. django_spire/core/templates/django_spire/button/base_button.html +1 -1
  114. django_spire/core/templates/django_spire/button/primary_dark_outlined_button.html +3 -0
  115. django_spire/core/templates/django_spire/dropdown/element/dropdown_link_element.html +12 -8
  116. django_spire/core/templates/django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html +2 -1
  117. django_spire/core/templates/django_spire/dropdown/ellipsis_dropdown.html +2 -1
  118. django_spire/core/templates/django_spire/navigation/accordion/nav_accordion.html +1 -1
  119. django_spire/core/templates/django_spire/navigation/elements/nav_link.html +1 -1
  120. django_spire/core/templates/django_spire/navigation/elements/nav_title_divider.html +1 -1
  121. django_spire/core/templates/django_spire/navigation/side_navigation.html +52 -8
  122. django_spire/core/templates/django_spire/navigation/top_navigation.html +8 -2
  123. django_spire/core/templates/django_spire/page/full_page.html +218 -11
  124. django_spire/core/utils.py +26 -2
  125. django_spire/knowledge/collection/models.py +24 -0
  126. django_spire/knowledge/collection/services/transformation_service.py +2 -1
  127. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +44 -42
  128. django_spire/knowledge/collection/urls/form_urls.py +1 -0
  129. django_spire/knowledge/collection/urls/page_urls.py +1 -0
  130. django_spire/knowledge/collection/views/form_views.py +13 -2
  131. django_spire/knowledge/collection/views/page_views.py +36 -4
  132. django_spire/knowledge/entry/models.py +32 -0
  133. django_spire/knowledge/entry/services/transformation_services.py +10 -10
  134. django_spire/knowledge/entry/version/block/data/data.py +2 -1
  135. django_spire/knowledge/entry/version/block/data/heading_data.py +2 -2
  136. django_spire/knowledge/entry/version/block/data/list/data.py +3 -4
  137. django_spire/knowledge/entry/version/block/data/maps.py +3 -5
  138. django_spire/knowledge/entry/version/block/data/text_data.py +2 -2
  139. django_spire/knowledge/entry/version/block/models.py +10 -7
  140. django_spire/knowledge/entry/version/block/services/factory_service.py +11 -10
  141. django_spire/knowledge/entry/version/block/tests/factories.py +6 -5
  142. django_spire/knowledge/entry/version/converters/docx_converter.py +1 -1
  143. django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +14 -12
  144. django_spire/knowledge/entry/version/seeding/seeder.py +3 -3
  145. django_spire/knowledge/entry/version/services/processor_service.py +36 -10
  146. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +1 -1
  147. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +1 -1
  148. django_spire/knowledge/entry/version/urls/page_urls.py +1 -1
  149. django_spire/knowledge/entry/version/views/json_views.py +5 -3
  150. django_spire/knowledge/entry/version/views/page_views.py +11 -12
  151. django_spire/knowledge/entry/version/views/redirect_views.py +1 -1
  152. django_spire/knowledge/entry/views/form_views.py +1 -1
  153. django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +33 -11
  154. django_spire/knowledge/intelligence/decoders/__init__.py +0 -0
  155. django_spire/knowledge/intelligence/{maps/collection_map.py → decoders/collection_decoder.py} +5 -5
  156. django_spire/knowledge/intelligence/{maps/entry_map.py → decoders/entry_decoder.py} +3 -3
  157. django_spire/knowledge/intelligence/intel/collection_intel.py +1 -0
  158. django_spire/knowledge/intelligence/intel/entry_intel.py +5 -2
  159. django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
  160. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +43 -51
  161. django_spire/knowledge/migrations/0007_alter_collection_options.py +17 -0
  162. django_spire/knowledge/static/django_spire/knowledge/entry/version/js/editor.js +24 -5
  163. django_spire/knowledge/templates/django_spire/knowledge/collection/card/top_level_list_card.html +21 -0
  164. django_spire/knowledge/templates/django_spire/knowledge/collection/component/x_collection_navigation.html +15 -6
  165. django_spire/knowledge/templates/django_spire/knowledge/collection/element/ellipsis_dropdown.html +22 -0
  166. django_spire/knowledge/templates/django_spire/knowledge/collection/form/form.html +21 -9
  167. django_spire/knowledge/templates/django_spire/knowledge/collection/item/collection_item.html +19 -0
  168. django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html +49 -0
  169. django_spire/knowledge/templates/django_spire/knowledge/collection/page/form_page.html +1 -1
  170. django_spire/knowledge/templates/django_spire/knowledge/container/container.html +33 -0
  171. django_spire/knowledge/templates/django_spire/knowledge/entry/file/page/list_page.html +1 -1
  172. django_spire/knowledge/templates/django_spire/knowledge/entry/item/list_item.html +2 -2
  173. django_spire/knowledge/templates/django_spire/knowledge/entry/page/form_page.html +1 -1
  174. django_spire/knowledge/templates/django_spire/knowledge/entry/page/import_form_page.html +1 -1
  175. django_spire/knowledge/templates/django_spire/knowledge/entry/version/container/detail_container.html +51 -45
  176. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +54 -0
  177. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/form_page.html +1 -1
  178. django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +18 -1
  179. django_spire/knowledge/templates/django_spire/knowledge/page/full_page.html +37 -0
  180. django_spire/knowledge/templates/django_spire/knowledge/page/home_page.html +2 -2
  181. django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/collection_sub_navigation_item.html +29 -0
  182. django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/entry_sub_navigation_item.html +20 -0
  183. django_spire/knowledge/templates/django_spire/knowledge/{navigation/content/navigation_content.html → sub_navigation/widget/collection_entry_sub_navigation_widget.html} +42 -19
  184. django_spire/knowledge/views/page_views.py +4 -4
  185. django_spire/settings.py +3 -3
  186. django_spire/theme/templates/django_spire/theme/card/badges_preview_card.html +0 -1
  187. django_spire/theme/templates/django_spire/theme/card/base_preview_card.html +1 -0
  188. django_spire/theme/templates/django_spire/theme/card/typography_preview_card.html +1 -1
  189. django_spire/theme/templates/django_spire/theme/example/form/example_form.html +2 -0
  190. django_spire/theme/templates/django_spire/theme/example/form/example_form_card.html +5 -0
  191. django_spire/theme/templates/django_spire/theme/page/badges_page.html +7 -9
  192. django_spire/theme/templates/django_spire/theme/page/borders_page.html +6 -9
  193. django_spire/theme/templates/django_spire/theme/page/buttons_page.html +6 -9
  194. django_spire/theme/templates/django_spire/theme/page/colors_page.html +6 -6
  195. django_spire/theme/templates/django_spire/theme/page/dashboard_page.html +57 -0
  196. django_spire/theme/templates/django_spire/theme/page/django_glue_page.html +7 -9
  197. django_spire/theme/templates/django_spire/theme/page/theme_page.html +24 -0
  198. django_spire/theme/templates/django_spire/theme/page/typography_page.html +6 -9
  199. django_spire/theme/templates/django_spire/theme/section/badge_section_card.html +5 -0
  200. django_spire/theme/templates/django_spire/theme/section/border_section_card.html +5 -0
  201. django_spire/theme/templates/django_spire/theme/section/button_section.html +1 -0
  202. django_spire/theme/templates/django_spire/theme/section/button_section_card.html +5 -0
  203. django_spire/theme/templates/django_spire/theme/section/color_section.html +10 -10
  204. django_spire/theme/templates/django_spire/theme/section/color_section_card.html +5 -0
  205. django_spire/theme/templates/django_spire/theme/section/typography_section.html +41 -1
  206. django_spire/theme/templates/django_spire/theme/section/typography_section_card.html +5 -0
  207. {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/METADATA +3 -3
  208. {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/RECORD +217 -178
  209. django_spire/ai/chat/intelligence/bots/chat_bot.py +0 -8
  210. django_spire/ai/chat/intelligence/maps/intent_llm_map.py +0 -10
  211. django_spire/ai/chat/templates/django_spire/ai/chat/page/home_page.html +0 -9
  212. django_spire/ai/chat/templates/django_spire/ai/chat/widget/chat_widget.html +0 -64
  213. django_spire/ai/chat/templates/django_spire/ai/chat/widget/search_chat_results_widget.html +0 -25
  214. django_spire/ai/chat/templates/django_spire/ai/chat/widget/search_chat_widget.html +0 -39
  215. django_spire/ai/chat/templates/django_spire/ai/chat/widget/select_chat_widget.html +0 -24
  216. django_spire/ai/chat/tools.py +0 -57
  217. django_spire/ai/sms/tools.py +0 -57
  218. django_spire/core/static/django_spire/font/Karla-Bold.ttf +0 -0
  219. django_spire/core/static/django_spire/font/Merriweather.ttf +0 -0
  220. django_spire/knowledge/context_processors.py +0 -18
  221. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/detail_page.html +0 -25
  222. django_spire/knowledge/templates/django_spire/knowledge/navigation/card/navigation_card.html +0 -9
  223. django_spire/knowledge/templates/django_spire/knowledge/navigation/item/collection/collection_item.html +0 -29
  224. django_spire/knowledge/templates/django_spire/knowledge/navigation/item/entry/entry_item.html +0 -18
  225. django_spire/knowledge/templates/django_spire/knowledge/navigation/page/full_page.html +0 -90
  226. /django_spire/ai/chat/intelligence/{bots → decoders}/__init__.py +0 -0
  227. /django_spire/ai/chat/{intelligence/maps → urls/template}/__init__.py +0 -0
  228. /django_spire/{knowledge/intelligence/maps → ai/chat/views/template}/__init__.py +0 -0
  229. /django_spire/knowledge/entry/version/{constants.py → consts.py} +0 -0
  230. /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
  231. /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
  232. {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/WHEEL +0 -0
  233. {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/licenses/LICENSE.md +0 -0
  234. {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/top_level.txt +0 -0
@@ -4,75 +4,124 @@
4
4
  <div
5
5
  class="row"
6
6
  x-data="{
7
- request_message_view: new ViewGlue('{% url "django_spire:ai:chat:message:request:new" %}'),
8
- load_messages_view: new ViewGlue('{% url "django_spire:ai:chat:message:load_chat" chat_id=chat.id %}'),
9
- chat_id: '{{ chat.id }}',
7
+ chat_id: '{{ chat.id|default:'0' }}',
8
+ chat_name: '{{ chat.name_shortened|default:'New Chat'|escapejs }}',
10
9
  message_body: '',
10
+ request_message_view: new ViewGlue('{% url "django_spire:ai:chat:message:request:new" %}'),
11
11
 
12
12
  init() {
13
- setTimeout(() => this.$refs.target_div.scrollTop = this.$refs.target_div.scrollHeight, 1500)
13
+ if (this.chat_id && this.chat_id !== '0' && this.chat_id !== '') {
14
+ this.load_messages()
15
+ }
16
+
17
+ setTimeout(() => {
18
+ if (this.$refs.target_div) {
19
+ this.smooth_scroll_to_bottom()
20
+ }
21
+ }, 1500)
14
22
 
15
23
  this.speech_recognition.onresult = () => {
16
24
  this.message_body = this.speech_transcript
17
25
  this.send_chat({message_body: this.message_body, synthesis_speech_for_response: true})
18
26
  }
19
27
  },
28
+
20
29
  load_messages() {
21
- this.load_messages_view.render_insert_adjacent(this.$refs.target_div, {})
30
+ let load_messages_view = new ViewGlue(`/django_spire/ai/chat/message/load/chat/${this.chat_id}/`)
31
+ load_messages_view.render_insert_adjacent(this.$refs.target_div, {})
22
32
  },
33
+
34
+ smooth_scroll_to_bottom() {
35
+ let target = this.$refs.target_div
36
+ let start = target.scrollTop
37
+ let end = target.scrollHeight - target.clientHeight
38
+ let duration = 400
39
+ let start_time = performance.now()
40
+
41
+ function animate(current_time) {
42
+ let elapsed = current_time - start_time
43
+ let progress = Math.min(elapsed / duration, 1)
44
+ let ease = progress * (2 - progress)
45
+
46
+ target.scrollTop = start + (end - start) * ease
47
+
48
+ if (progress < 1) {
49
+ requestAnimationFrame(animate)
50
+ }
51
+ }
52
+
53
+ requestAnimationFrame(animate)
54
+ },
55
+
23
56
  async send_chat({message_body = '', synthesis_speech_for_response = false}) {
24
57
  if (message_body !== '') {
25
- await this.request_message_view.render_insert_adjacent(
58
+ let was_new_chat = (this.chat_id === '0' || this.chat_id === 0 || this.chat_id === '')
59
+
60
+ let response_html = await this.request_message_view.render_insert_adjacent(
26
61
  this.$refs.target_div,
27
62
  {
28
- message_body: message_body,
29
63
  chat_id: this.chat_id,
64
+ message_body: message_body,
30
65
  synthesis_speech: synthesis_speech_for_response
31
66
  }
32
67
  )
68
+
69
+ if (was_new_chat) {
70
+ this.chat_name = message_body
71
+
72
+ let message_element = this.$refs.target_div.querySelector('[data-message-chat-id]')
73
+
74
+ if (message_element) {
75
+ this.chat_id = message_element.dataset.messageChatId
76
+ $dispatch('new-chat-created', {chat_id: parseInt(this.chat_id)})
77
+ }
78
+ }
79
+
33
80
  this.message_body = ''
81
+ $dispatch('chat-loaded')
82
+ }
83
+ },
84
+
85
+ speak_text(text) {
86
+ if ('speechSynthesis' in window) {
87
+ let utterance = new SpeechSynthesisUtterance(text)
88
+ window.speechSynthesis.speak(utterance)
34
89
  }
35
90
  }
36
91
  }"
92
+ @send-message.window="send_chat({message_body: $event.detail.message_body})" @speak.window="speak_text($event.detail.text)"
37
93
  >
38
94
  <div class="col-12">
39
- <div class="row px-0 border-bottom">
40
- <div class="col-auto ps-2 pe-0 h-100 d-block d-md-none text-app-primary"
41
- style="font-size: 1.6rem; margin-top: -0.2rem;">
42
- <i x-show="!select_chat_visible" @click="open_chat_select()" class="bi bi-chat-left-text"></i>
43
- </div>
44
- <div class="col text-center fw-bold pb-2">
45
- {{ chat.name_shortened }}
95
+ <div class="row border-bottom px-0">
96
+ <div class="col text-center pb-2" x-text="chat_name">
46
97
  </div>
47
98
  </div>
48
99
 
49
100
  <div class="row mb-3">
50
- <div x-init="load_messages()" x-ref="target_div" class="col-12 overflow-auto p-4"
51
- style="height: calc(100vh - 90px - var(--app-top-navigation-height));">
101
+ <div class="col-12 overflow-auto p-4" style="height: calc(100vh - 90px - var(--app-top-navigation-height));" x-ref="target_div">
52
102
  </div>
53
103
  </div>
54
104
 
55
105
  <div class="row">
56
106
  <div class="col-auto text-center ps-2 pe-1">
57
- <button class="btn btn-app-primary-outlined" x-show="!speech_listening" @click="start_listening()">
107
+ <button @click="start_listening()" class="btn btn-app-primary-outlined" x-show="!speech_listening">
58
108
  <i class="bi bi-mic"></i>
59
109
  </button>
60
- <button class="btn btn-app-danger" x-show="speech_listening" @click="stop_listening()">
110
+ <button @click="stop_listening()" class="btn btn-app-danger" x-show="speech_listening">
61
111
  <i class="bi bi-mic-fill"></i>
62
112
  </button>
63
113
  </div>
114
+
64
115
  <div class="col text-center px-1">
65
- <input type="text" x-init="$el.focus()" x-model="message_body" @keyup.enter="send_chat({message_body: message_body})"
66
- class="form-control" autocomplete="off" name="request_message">
116
+ <input @keyup.enter="send_chat({message_body: message_body})" autocomplete="off" class="form-control" name="request_message" type="text" x-init="$el.focus()" x-model="message_body">
67
117
  </div>
118
+
68
119
  <div class="col-auto text-center ps-1 pe-2">
69
- <button class="btn btn-app-primary-outlined" @click="send_chat({message_body: message_body})">
120
+ <button @click="send_chat({message_body: message_body})" class="btn btn-app-primary">
70
121
  <i class="bi bi-send"></i>
71
122
  </button>
72
123
  </div>
73
124
  </div>
74
-
75
125
  </div>
76
126
  </div>
77
-
78
127
  {% endblock %}
@@ -0,0 +1,42 @@
1
+ <div
2
+ class="row"
3
+ x-data="{
4
+ active_chat_id: 0,
5
+ chat_content_type: '',
6
+ chat_load_recent_view: new ViewGlue('{% url "django_spire:ai:chat:template:recent" %}'),
7
+ chat_object_id: '',
8
+
9
+ init() {
10
+ this.load_recent_chats()
11
+ $dispatch('load-chat', {chat_id: 0})
12
+ },
13
+ async load_recent_chats() {
14
+ await this.chat_load_recent_view.render_inner(this.$refs.chat_recent_list)
15
+ $dispatch('chat-activated', {chat_id: this.active_chat_id})
16
+ }
17
+ }"
18
+ @chat-loaded.window="load_recent_chats()"
19
+ @new-chat-created.window="active_chat_id = $event.detail.chat_id; $dispatch('chat-activated', {chat_id: $event.detail.chat_id})"
20
+ @load-chat.window="active_chat_id = $event.detail.chat_id; $dispatch('chat-activated', {chat_id: $event.detail.chat_id})">
21
+
22
+ <div class="col-12 px-0">
23
+ <div class="px-2 pb-3">
24
+ <button @click="$dispatch('load-chat', {chat_id: 0})" class="btn btn-app-primary-outlined p-1 w-100">
25
+ <i class="bi bi-plus-lg me-2"></i>New Chat
26
+ </button>
27
+ </div>
28
+
29
+ <div class="border-top border-app-primary"></div>
30
+
31
+ <div class="px-3 py-2">
32
+ <div class="text-muted text-uppercase" style="font-size: 0.75rem; font-weight: 600; letter-spacing: 0.5px;">
33
+ Recent Chats
34
+ </div>
35
+ </div>
36
+
37
+ <div class="overflow-auto" style="height: calc(100vh - 180px - var(--app-top-navigation-height));">
38
+ <div class="px-2" x-ref="chat_recent_list">
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
@@ -1,15 +1,17 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.ai.chat.tests.test_urls.factories import create_test_chat
2
4
  from django_spire.core.tests.test_cases import BaseTestCase
3
5
  from django.urls import reverse
4
6
 
5
7
 
6
8
  class ChatJsonUrlTests(BaseTestCase):
7
- def setUp(self):
9
+ def setUp(self) -> None:
8
10
  super().setUp()
9
11
 
10
12
  self.test_chat = create_test_chat(user=self.super_user)
11
13
 
12
- def test_delete_view_url_path(self):
14
+ def test_delete_view_url_path(self) -> None:
13
15
  response = self.client.post(
14
16
  reverse(
15
17
  'django_spire:ai:chat:json:delete',
@@ -6,5 +6,5 @@ urlpatterns = [
6
6
  path('json/', include('django_spire.ai.chat.urls.json_urls', namespace='json')),
7
7
  path('message/', include('django_spire.ai.chat.urls.message_urls', namespace='message')),
8
8
  path('page/', include('django_spire.ai.chat.urls.page_urls', namespace='page')),
9
- path('template/', include('django_spire.ai.chat.urls.template_urls', namespace='template')),
9
+ path('template/', include('django_spire.ai.chat.urls.template.template_urls', namespace='template')),
10
10
  ]
@@ -8,5 +8,5 @@ app_name = 'json'
8
8
  urlpatterns = [
9
9
  path('<int:pk>/delete', json_views.delete_chat_view, name='delete'),
10
10
  path('<int:pk>/rename', json_views.rename_chat_view, name='rename'),
11
- path('workflow_process', json_views.chat_workflow_process_view, name='workflow_process'),
11
+ path('workflow_process', json_views.chat_workflow_view, name='workflow_process'),
12
12
  ]
@@ -6,5 +6,5 @@ from django_spire.ai.chat.views import page_views
6
6
  app_name = 'page'
7
7
 
8
8
  urlpatterns = [
9
- path('home/', page_views.home_view, name='home'),
9
+ path('home/', page_views.chat_view, name='home'),
10
10
  ]
@@ -1,6 +1,6 @@
1
1
  from django.urls import path
2
2
 
3
- from django_spire.ai.chat.views import template_views
3
+ from django_spire.ai.chat.views.template import template_views
4
4
 
5
5
 
6
6
  app_name = 'template'
@@ -3,10 +3,11 @@ from __future__ import annotations
3
3
  import json
4
4
 
5
5
  from typing import TYPE_CHECKING
6
+
6
7
  from django.http import JsonResponse
7
8
 
9
+ from django_spire.ai.chat.intelligence.workflows.chat_workflow import chat_workflow
8
10
  from django_spire.ai.chat.models import Chat
9
- from django_spire.ai.chat.tools import chat_workflow_process
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from django.core.handlers.wsgi import WSGIRequest
@@ -50,6 +51,6 @@ def rename_chat_view(request: WSGIRequest, pk: int) -> JsonResponse:
50
51
  return JsonResponse({'type': 'success'})
51
52
 
52
53
 
53
- def chat_workflow_process_view(request: WSGIRequest) -> JsonResponse:
54
+ def chat_workflow_view(request: WSGIRequest) -> JsonResponse:
54
55
  body = json.loads(request.body.decode('utf-8'))
55
- return JsonResponse({'response': chat_workflow_process(request, body)})
56
+ return JsonResponse({'response': chat_workflow(request, body)})
@@ -2,28 +2,45 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
 
5
+ from typing import TYPE_CHECKING
6
+
5
7
  from django.conf import settings
6
8
  from django.http import HttpResponse
9
+ from django.utils.timezone import now
7
10
 
11
+ from django_spire.ai.chat.choices import MessageResponseType
8
12
  from django_spire.ai.chat.message_intel import DefaultMessageIntel
9
13
  from django_spire.ai.chat.models import Chat
10
- from django_spire.ai.chat.responses import MessageResponseGroup, MessageResponse
11
- from django_spire.ai.chat.choices import MessageResponseType
12
- from django_spire.consts import AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME
14
+ from django_spire.ai.chat.responses import MessageResponse, MessageResponseGroup
13
15
 
16
+ if TYPE_CHECKING:
17
+ from django.core.handlers.wsgi import WSGIRequest
14
18
 
15
- def request_message_render_view(request):
19
+
20
+ def request_message_render_view(request: WSGIRequest) -> HttpResponse:
16
21
  body_data = json.loads(request.body)
17
22
 
18
- chat = (
19
- Chat.objects
20
- .by_user(request.user)
21
- .get(id=body_data['chat_id'])
22
- )
23
+ chat_id = body_data['chat_id']
23
24
 
24
- if chat.is_empty:
25
- chat.name = body_data['message_body']
26
- chat.save()
25
+ current_datetime = now()
26
+ formatted_timestamp = current_datetime.strftime('%b %d, %Y at %I:%M %p')
27
+
28
+ if chat_id in {0, '0', ''}:
29
+ chat = Chat.objects.create(
30
+ user=request.user,
31
+ name=body_data['message_body'],
32
+ last_message_datetime=current_datetime
33
+ )
34
+ else:
35
+ chat = (
36
+ Chat.objects
37
+ .by_user(request.user)
38
+ .get(id=chat_id)
39
+ )
40
+
41
+ if chat.is_empty:
42
+ chat.name = body_data['message_body']
43
+ chat.save()
27
44
 
28
45
  message_response_group = MessageResponseGroup()
29
46
 
@@ -32,7 +49,8 @@ def request_message_render_view(request):
32
49
  sender='You',
33
50
  message_intel=DefaultMessageIntel(
34
51
  text=body_data['message_body']
35
- )
52
+ ),
53
+ message_timestamp=formatted_timestamp
36
54
  )
37
55
 
38
56
  message_response_group.add_message_response(
@@ -56,7 +74,7 @@ def request_message_render_view(request):
56
74
  message_response_group.render_to_html_string(
57
75
  context_data={
58
76
  "chat_id": chat.id,
59
- "chat_workflow_name": getattr(settings, AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME),
77
+ "chat_workflow_name": settings.AI_PERSONA_NAME,
60
78
  }
61
79
  )
62
80
  )
@@ -2,38 +2,41 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
 
5
+ from typing import TYPE_CHECKING
6
+
5
7
  from django.conf import settings
6
8
  from django.http import HttpResponse
9
+ from django.utils.timezone import now
7
10
 
11
+ from django_spire.ai.chat.intelligence.workflows.chat_workflow import chat_workflow
8
12
  from django_spire.ai.chat.models import Chat
9
13
  from django_spire.ai.chat.responses import MessageResponse
10
14
  from django_spire.ai.chat.choices import MessageResponseType
11
- from django_spire.ai.chat.tools import chat_workflow_process
12
- from django_spire.consts import AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME
13
-
14
15
 
15
- def response_message_render_view(request):
16
- body_data = json.loads(request.body)
16
+ if TYPE_CHECKING:
17
+ from django.core.handlers.wsgi import WSGIRequest
17
18
 
18
- chat_workflow_name = getattr(settings, AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME)
19
19
 
20
- if chat_workflow_name is None:
21
- message = f'"{AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME}" must be set in the django settings.'
22
- raise ValueError(message)
20
+ def response_message_render_view(request: WSGIRequest) -> HttpResponse:
21
+ body_data = json.loads(request.body)
23
22
 
24
23
  chat = Chat.objects.by_user(request.user).get(id=body_data['chat_id'])
25
24
 
26
- message_intel = chat_workflow_process(
25
+ message_intel = chat_workflow(
27
26
  request,
28
27
  body_data['message_body'],
29
28
  message_history=chat.generate_message_history(),
30
29
  )
31
30
 
31
+ current_datetime = now()
32
+ formatted_timestamp = current_datetime.strftime('%b %d, %Y at %I:%M %p')
33
+
32
34
  response_message = MessageResponse(
33
35
  type=MessageResponseType.RESPONSE,
34
- sender=chat_workflow_name,
36
+ sender=settings.AI_PERSONA_NAME,
35
37
  message_intel=message_intel,
36
38
  synthesis_speech=body_data.get('synthesis_speech', False),
39
+ message_timestamp=formatted_timestamp
37
40
  )
38
41
 
39
42
  chat.add_message_response(response_message)
@@ -1,12 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.http import HttpResponse
4
6
 
5
7
  from django_spire.ai.chat.models import Chat
6
8
  from django_spire.ai.chat.responses import MessageResponseGroup
7
9
 
10
+ if TYPE_CHECKING:
11
+ from django.core.handlers.wsgi import WSGIRequest
12
+
8
13
 
9
- def load_messages_render_view(request, chat_id):
14
+ def load_messages_render_view(request: WSGIRequest, chat_id: int) -> HttpResponse:
10
15
  chat = (
11
16
  Chat.objects
12
17
  .by_user(request.user)
@@ -24,6 +29,7 @@ def load_messages_render_view(request, chat_id):
24
29
  message_group.render_to_html_string(
25
30
  {
26
31
  'chat_id': chat.id,
32
+ 'is_loading': True,
27
33
  }
28
34
  )
29
35
  )
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
12
12
 
13
13
 
14
14
  @AppAuthController('ai_chat').permission_required('can_delete')
15
- def home_view(request: WSGIRequest) -> TemplateResponse:
15
+ def chat_view(request: WSGIRequest) -> TemplateResponse:
16
16
  breadcrumbs = Breadcrumbs()
17
17
  breadcrumbs.add_breadcrumb(name='AI Chat')
18
18
 
@@ -22,5 +22,5 @@ def home_view(request: WSGIRequest) -> TemplateResponse:
22
22
  page_title='AI Chat',
23
23
  page_description='Chat with AI',
24
24
  breadcrumbs=breadcrumbs,
25
- template='django_spire/ai/chat/page/home_page.html',
25
+ template='django_spire/ai/chat/page/chat_page.html',
26
26
  )
@@ -23,13 +23,7 @@ def dialog_widget_view(request) -> TemplateResponse:
23
23
  chat_id = body_data['chat_id']
24
24
 
25
25
  if chat_id == 0:
26
- chat = Chat.objects.get_empty_or_create(
27
- user=request.user
28
- )
29
- chat.name = 'New Chat'
30
- chat.last_message_datetime = now()
31
- chat.save()
32
-
26
+ chat = None
33
27
  else:
34
28
  chat = Chat.objects.get(
35
29
  id=chat_id,
File without changes
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib import admin
4
+
5
+ from django_spire.ai.context import models
6
+
7
+
8
+ @admin.register(models.Organization)
9
+ class OrganizationAdmin(admin.ModelAdmin):
10
+ list_display = ('id', 'name', 'created_datetime', 'is_deleted')
11
+
12
+
13
+ @admin.register(models.Person)
14
+ class PersonAdmin(admin.ModelAdmin):
15
+ list_display = ('id', 'first_name', 'created_datetime', 'is_deleted')
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from django.apps import AppConfig
4
+
5
+ from django_spire.utils import check_required_apps
6
+
7
+
8
+ class AiContextConfig(AppConfig):
9
+ default_auto_field = 'django.db.models.BigAutoField'
10
+ name = 'django_spire.ai.context'
11
+ label = 'django_spire_ai_context'
12
+
13
+ REQUIRED_APPS = ('django_spire_ai',)
14
+
15
+ def ready(self) -> None:
16
+ check_required_apps(self.label)
@@ -0,0 +1,11 @@
1
+ from django.db.models import TextChoices
2
+
3
+
4
+ class PersonRoleChoices(TextChoices):
5
+ ADMIN = 'admin', 'Admin'
6
+ HUMAN_RESOURCES = 'human_resources', 'Human Resources'
7
+ SALES = 'sales', 'Sales'
8
+ MANAGER = 'manager', 'Manager'
9
+ MARKETING = 'marketing', 'Marketing'
10
+ TECHNICAL = 'technical', 'Technical'
11
+ IT_SUPPORT = 'it_support', 'IT Support'
File without changes
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from dandy import Prompt
4
+ from django.forms import model_to_dict
5
+
6
+ from django_spire.ai.context.models import Organization
7
+
8
+
9
+ def organization_info_prompt() -> Prompt:
10
+ org = Organization.objects.get_only_or_none()
11
+
12
+ if org is None:
13
+ return Prompt().text('There is no organization information available.')
14
+
15
+ org_dict = model_to_dict(
16
+ org, exclude=['id', 'created_datetime', 'is_active', 'is_deleted']
17
+ )
18
+
19
+ return Prompt().heading('Organization Information').dict(org_dict)
@@ -0,0 +1,67 @@
1
+ # Generated by Django 5.2.7 on 2025-10-27 16:34
2
+
3
+ import django.db.models.deletion
4
+ import django.utils.timezone
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ initial = True
11
+
12
+ dependencies = [
13
+ ('django_spire_auth_user', '0001_initial'),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name='Organization',
19
+ fields=[
20
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+ ('is_active', models.BooleanField(default=True, editable=False)),
22
+ ('is_deleted', models.BooleanField(default=False, editable=False)),
23
+ ('created_datetime', models.DateTimeField(default=django.utils.timezone.localtime, editable=False)),
24
+ ('name', models.CharField(blank=True, max_length=255, null=True)),
25
+ ('legal_name', models.CharField(blank=True, max_length=255, null=True)),
26
+ ('description', models.TextField(blank=True, null=True)),
27
+ ('sector', models.CharField(blank=True, max_length=255, null=True)),
28
+ ('sub_sector', models.CharField(blank=True, max_length=255, null=True)),
29
+ ('website', models.URLField(blank=True, max_length=255, null=True)),
30
+ ('street_address', models.CharField(blank=True, max_length=255, null=True)),
31
+ ('unit_number', models.CharField(blank=True, max_length=32, null=True)),
32
+ ('city', models.CharField(blank=True, max_length=255, null=True)),
33
+ ('province', models.CharField(blank=True, max_length=255, null=True)),
34
+ ('postal_code', models.CharField(blank=True, max_length=16, null=True)),
35
+ ('country', models.CharField(blank=True, max_length=255, null=True)),
36
+ ('phone', models.CharField(blank=True, max_length=255, null=True)),
37
+ ('email', models.EmailField(blank=True, max_length=255, null=True)),
38
+ ],
39
+ options={
40
+ 'verbose_name': 'Organization Context',
41
+ 'verbose_name_plural': 'Organization Context',
42
+ 'db_table': 'django_spire_ai_context_organization',
43
+ },
44
+ ),
45
+ migrations.CreateModel(
46
+ name='Person',
47
+ fields=[
48
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
49
+ ('is_active', models.BooleanField(default=True, editable=False)),
50
+ ('is_deleted', models.BooleanField(default=False, editable=False)),
51
+ ('created_datetime', models.DateTimeField(default=django.utils.timezone.localtime, editable=False)),
52
+ ('is_internal_to_organization', models.BooleanField(default=True)),
53
+ ('role', models.CharField(choices=[('admin', 'Admin'), ('human_resources', 'Human Resources'), ('sales', 'Sales'), ('manager', 'Manager'), ('marketing', 'Marketing'), ('technical', 'Technical'), ('it_support', 'IT Support')], default='admin', max_length=16)),
54
+ ('role_details', models.TextField(blank=True, null=True)),
55
+ ('first_name', models.CharField(blank=True, max_length=255, null=True)),
56
+ ('last_name', models.CharField(blank=True, max_length=255, null=True)),
57
+ ('phone', models.CharField(blank=True, max_length=255, null=True)),
58
+ ('email', models.EmailField(blank=True, max_length=255, null=True)),
59
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ai_context_people', related_query_name='ai_context_person', to='django_spire_auth_user.authuser')),
60
+ ],
61
+ options={
62
+ 'verbose_name': 'Person Context',
63
+ 'verbose_name_plural': 'People Context',
64
+ 'db_table': 'django_spire_ai_context_person',
65
+ },
66
+ ),
67
+ ]
File without changes