django-spire 0.20.4__py3-none-any.whl → 0.21.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 (39) hide show
  1. django_spire/ai/chat/intelligence/decoders/intent_decoder.py +49 -0
  2. django_spire/ai/chat/intelligence/workflows/chat_workflow.py +19 -53
  3. django_spire/ai/chat/message_intel.py +15 -7
  4. django_spire/ai/chat/models.py +6 -5
  5. django_spire/ai/chat/router.py +105 -0
  6. django_spire/ai/chat/templates/django_spire/ai/chat/element/recent_chat_select_element.html +1 -1
  7. django_spire/ai/chat/templates/django_spire/ai/chat/message/message.html +2 -2
  8. django_spire/ai/chat/templates/django_spire/ai/chat/widget/selection_widget.html +1 -1
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +110 -0
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +113 -0
  11. django_spire/ai/chat/tests/test_router/test_integration.py +147 -0
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +141 -0
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +67 -0
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +92 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +1 -1
  16. django_spire/ai/chat/views/message_request_views.py +5 -3
  17. django_spire/ai/chat/views/message_response_views.py +2 -2
  18. django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +8 -8
  19. django_spire/ai/sms/models.py +1 -1
  20. django_spire/ai/tests/test_ai.py +1 -1
  21. django_spire/consts.py +1 -1
  22. django_spire/core/static/django_spire/css/app-navigation.css +4 -4
  23. django_spire/core/static/django_spire/css/app-side-panel.css +0 -45
  24. django_spire/core/templates/django_spire/navigation/top_navigation.html +42 -38
  25. django_spire/core/templates/django_spire/page/full_page.html +69 -47
  26. django_spire/knowledge/collection/seeding/seeder.py +2 -2
  27. django_spire/knowledge/entry/seeding/seeder.py +10 -5
  28. django_spire/knowledge/intelligence/decoders/entry_decoder.py +4 -1
  29. django_spire/knowledge/intelligence/intel/message_intel.py +1 -1
  30. django_spire/knowledge/intelligence/router.py +26 -0
  31. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -3
  32. django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/entry_sub_navigation_item.html +1 -0
  33. django_spire/settings.py +13 -6
  34. {django_spire-0.20.4.dist-info → django_spire-0.21.1.dist-info}/METADATA +1 -1
  35. {django_spire-0.20.4.dist-info → django_spire-0.21.1.dist-info}/RECORD +38 -30
  36. django_spire/ai/chat/intelligence/decoders/tools.py +0 -34
  37. {django_spire-0.20.4.dist-info → django_spire-0.21.1.dist-info}/WHEEL +0 -0
  38. {django_spire-0.20.4.dist-info → django_spire-0.21.1.dist-info}/licenses/LICENSE.md +0 -0
  39. {django_spire-0.20.4.dist-info → django_spire-0.21.1.dist-info}/top_level.txt +0 -0
@@ -37,49 +37,53 @@
37
37
  </div>
38
38
  {% endblock %}
39
39
 
40
- {% block notification_icon %}
41
- <div>
42
- {% include 'django_spire/notification/app/element/notification_bell.html' %}
43
- </div>
44
- {% endblock %}
40
+ {% if request.user.is_authenticated %}
41
+ {% block notification_icon %}
42
+ <div>
43
+ {% include 'django_spire/notification/app/element/notification_bell.html' %}
44
+ </div>
45
+ {% endblock %}
46
+ {% endif %}
45
47
 
46
- <div class="dropdown">
47
- <i
48
- class="bi bi-person-circle fs-2 mx-2 cursor-pointer"
49
- id="dropdownMenuButton1"
50
- data-bs-toggle="dropdown"
51
- aria-expanded="false"
52
- >
53
- </i>
54
- <ul class="dropdown-menu py-0 bg-app-layer-one" aria-labelledby="dropdownMenuButton1">
55
- {% if request.user.is_superuser %}
56
- <li class="py-0 ">
57
- <a class="dropdown-item fs--1 bg-app-layer-three-hover"
58
- href="{% url 'admin:index' %}" target="_blank">
59
- Admin Panel
48
+ {% if request.user.is_authenticated %}
49
+ <div class="dropdown">
50
+ <i
51
+ class="bi bi-person-circle fs-2 mx-2 cursor-pointer"
52
+ id="dropdownMenuButton1"
53
+ data-bs-toggle="dropdown"
54
+ aria-expanded="false"
55
+ >
56
+ </i>
57
+ <ul class="dropdown-menu py-0 bg-app-layer-one" aria-labelledby="dropdownMenuButton1">
58
+ {% if request.user.is_superuser %}
59
+ <li class="py-0">
60
+ <a class="dropdown-item fs--1 bg-app-layer-three-hover"
61
+ href="{% url 'admin:index' %}" target="_blank">
62
+ Admin Panel
63
+ </a>
64
+ </li>
65
+ <li class="py-0">
66
+ <a class="dropdown-item fs--1 bg-app-layer-three-hover"
67
+ href="{% url 'django_spire:theme:page:dashboard' %}" target="_blank">
68
+ Theme Dashboard
69
+ </a>
70
+ </li>
71
+ {% endif %}
72
+ <li class="py-0">
73
+ <a href="{% url 'django_spire:auth:admin:password_change' %}"
74
+ class="dropdown-item fs--1 bg-app-layer-three-hover">
75
+ Change Password
60
76
  </a>
61
77
  </li>
62
- <li class="py-0 ">
63
- <a class="dropdown-item fs--1 bg-app-layer-three-hover"
64
- href="{% url 'django_spire:theme:page:dashboard' %}" target="_blank">
65
- Theme Dashboard
78
+ <li class="py-0">
79
+ <a href="{% url 'django_spire:auth:redirect:logout' %}"
80
+ class="dropdown-item fs--1 bg-app-layer-three-hover">
81
+ Logout
66
82
  </a>
67
83
  </li>
68
- {% endif %}
69
- <li class="py-0">
70
- <a href="{% url 'django_spire:auth:admin:password_change' %}"
71
- class="dropdown-item fs--1 bg-app-layer-three-hover">
72
- Change Password
73
- </a>
74
- </li>
75
- <li class="py-0">
76
- <a href="{% url 'django_spire:auth:redirect:logout' %}"
77
- class="dropdown-item fs--1 bg-app-layer-three-hover">
78
- Logout
79
- </a>
80
- </li>
81
- </ul>
82
- </div>
84
+ </ul>
85
+ </div>
86
+ {% endif %}
83
87
 
84
88
  <div class="col-auto d-lg-none">
85
89
  {% include 'django_spire/navigation/mobile_navigation.html' %}
@@ -25,7 +25,8 @@
25
25
  info_nav_max_width: 600,
26
26
  info_nav_min_width: 200,
27
27
  info_nav_width: 400,
28
- is_mobile: window.innerWidth < 992,
28
+ is_mobile: false,
29
+ is_narrow_viewport: false,
29
30
  is_resizing_info: false,
30
31
  is_resizing_sub: false,
31
32
  resize_start_width: 0,
@@ -37,7 +38,36 @@
37
38
  sub_nav_min_width: 300,
38
39
  sub_nav_width: 300,
39
40
 
41
+ is_touch_device() {
42
+ return window.matchMedia('(pointer: coarse)').matches || 'ontouchstart' in window;
43
+ },
44
+
45
+ update_viewport_state() {
46
+ const was_narrow = this.is_narrow_viewport;
47
+ const is_now_narrow = window.innerWidth < 992;
48
+ this.is_narrow_viewport = is_now_narrow;
49
+
50
+ if (!was_narrow && is_now_narrow) {
51
+ this.show_sub_nav = false;
52
+ this.show_info_nav = false;
53
+ }
54
+
55
+ if (was_narrow && !is_now_narrow) {
56
+ this.$nextTick(() => {
57
+ this.show_sub_nav = false;
58
+ this.show_info_nav = false;
59
+ });
60
+ }
61
+
62
+ if (!this.is_narrow_viewport) {
63
+ this.show_info_nav = false;
64
+ }
65
+ },
66
+
40
67
  init() {
68
+ this.is_mobile = this.is_touch_device();
69
+ this.update_viewport_state();
70
+
41
71
  const urlParams = new URLSearchParams(window.location.search);
42
72
 
43
73
  if (urlParams.has('show_sub_nav')) {
@@ -46,12 +76,12 @@
46
76
  this.show_sub_nav = window.innerWidth >= 992
47
77
  }
48
78
 
79
+ if (window.innerWidth < 992) {
80
+ this.show_info_nav = false;
81
+ }
82
+
49
83
  window.addEventListener('resize', () => {
50
- this.is_mobile = window.innerWidth < 992
51
- if (this.is_mobile) {
52
- this.show_sub_nav = false
53
- this.show_info_nav = false
54
- }
84
+ this.update_viewport_state();
55
85
  })
56
86
  },
57
87
 
@@ -74,10 +104,18 @@
74
104
  }
75
105
  },
76
106
 
107
+ close_info() {
108
+ this.show_info_nav = false
109
+ },
110
+
111
+ close_sub() {
112
+ this.show_sub_nav = false
113
+ },
114
+
77
115
  toggle_info() {
78
116
  this.show_info_nav = !this.show_info_nav
79
117
 
80
- if (this.is_mobile) {
118
+ if (this.is_mobile && this.is_narrow_viewport) {
81
119
  this.show_sub_nav = false
82
120
  }
83
121
  },
@@ -85,7 +123,7 @@
85
123
  toggle_sub() {
86
124
  this.show_sub_nav = !this.show_sub_nav
87
125
 
88
- if (this.is_mobile) {
126
+ if (this.is_mobile && this.is_narrow_viewport) {
89
127
  this.show_info_nav = false
90
128
  }
91
129
  }
@@ -108,27 +146,20 @@
108
146
 
109
147
  <div class="row">
110
148
  <div
111
- :class="is_mobile ? '' : 'position-relative col-auto'"
112
- :style="is_mobile ? 'position: fixed; width: 100vw; height: 100vh; left: 0; top: 0; z-index: 1040;' : (sub_nav_has_been_resized ? `width: ${sub_nav_width}px; min-width: ${sub_nav_width}px; max-width: ${sub_nav_width}px;` : `min-width: ${sub_nav_min_width}px;`)"
149
+ :class="(is_mobile && is_narrow_viewport) ? '' : 'position-relative col-auto'"
150
+ :style="(is_mobile && is_narrow_viewport) ? 'position: fixed; width: 100vw; height: 100vh; left: 0; top: 0; z-index: 1040;' : (sub_nav_has_been_resized ? `width: ${sub_nav_width}px; min-width: ${sub_nav_width}px; max-width: ${sub_nav_width}px;` : `min-width: ${sub_nav_min_width}px;`)"
113
151
  x-ref="sub_nav_container"
114
152
  x-show="has_content($refs.full_page_sub_navigation) && show_sub_nav"
115
- x-transition:enter="side-panel-transition-enter"
116
- x-transition:enter-end="side-panel-transition-enter-end"
117
- x-transition:enter-start="side-panel-transition-enter-start-left"
118
- x-transition:leave="side-panel-transition-leave"
119
- x-transition:leave-end="side-panel-transition-leave-end-left"
120
- x-transition:leave-start="side-panel-transition-leave-start"
153
+ x-transition
121
154
  >
122
155
  <div
123
- :style="is_mobile ? 'height: 100vh;' : ''"
156
+ :style="(is_mobile && is_narrow_viewport) ? 'height: 100vh;' : ''"
124
157
  class="row sticky-top border-end border-app-primary side-panel overflow-y-auto"
125
158
  >
126
159
  <div class="col">
127
160
  <div class="row pb-2 align-items-center">
128
161
  <div class="col-auto px-1" style="visibility: hidden;">
129
- <button class="btn-close-panel" type="button">
130
- <i class="bi bi-x-lg f5"></i>
131
- </button>
162
+ {% include 'django_spire/button/primary_button.html' with button_icon='bi bi-x-lg' %}
132
163
  </div>
133
164
  <div class="col h5 text-app-primary text-center text-nowrap overflow-hidden text-truncate mb-0" style="min-width: 0;">
134
165
  {% block full_page_sub_navigation_title %}
@@ -139,18 +170,16 @@
139
170
  {% endblock %}
140
171
  </div>
141
172
  <div class="col-auto px-1">
142
- <button
143
- @click="show_sub_nav = false"
144
- class="btn-close-panel"
145
- type="button"
146
- >
147
- <i class="bi bi-x-lg f5"></i>
148
- </button>
173
+ {% include 'django_spire/button/primary_button.html' with button_icon='bi bi-x-lg' x_button_click='close_sub()' %}
149
174
  </div>
150
175
  </div>
151
176
 
152
177
  <div class="row">
153
- <div class="col" x-ref="full_page_sub_navigation">
178
+ <div
179
+ @click.capture="if (is_narrow_viewport && $event.target.closest('[data-closes-nav]')) { $nextTick(() => { show_sub_nav = false }) }"
180
+ class="col"
181
+ x-ref="full_page_sub_navigation"
182
+ >
154
183
  {% block full_page_sub_navigation %}
155
184
  {% endblock %}
156
185
  </div>
@@ -168,7 +197,7 @@
168
197
 
169
198
  <div
170
199
  class="panel-toggle-container panel-toggle-container-left"
171
- x-show="has_content($refs.full_page_sub_navigation) && !show_sub_nav && !(is_mobile && show_info_nav)"
200
+ x-show="has_content($refs.full_page_sub_navigation) && !show_sub_nav && !((is_mobile && is_narrow_viewport) && show_info_nav)"
172
201
  >
173
202
  {% include 'django_spire/button/primary_button.html' with button_icon='bi bi-chevron-right' x_button_click='toggle_sub()' %}
174
203
  </div>
@@ -183,24 +212,19 @@
183
212
 
184
213
  <div
185
214
  class="col-auto panel-toggle-container panel-toggle-container-right"
186
- x-show="has_content($refs.full_page_info_navigation) && !show_info_nav && !(is_mobile && show_sub_nav)"
215
+ x-show="has_content($refs.full_page_info_navigation) && !show_info_nav && !((is_mobile && is_narrow_viewport) && show_sub_nav)"
187
216
  >
188
217
  {% include 'django_spire/button/primary_button.html' with button_icon='bi bi-chevron-left' x_button_click='toggle_info()' %}
189
218
  </div>
190
219
 
191
220
  <div
192
- :class="is_mobile ? '' : 'position-relative col-auto'"
193
- :style="is_mobile ? 'position: fixed; width: 100vw; height: 100vh; right: 0; top: 0; z-index: 1040;' : `width: ${info_nav_width}px; min-width: ${info_nav_width}px; max-width: ${info_nav_width}px; margin-left: -${info_nav_width}px; z-index: 1040;`"
221
+ :class="(is_mobile && is_narrow_viewport) ? '' : 'position-relative col-auto'"
222
+ :style="(is_mobile && is_narrow_viewport) ? 'position: fixed; width: 100vw; height: 100vh; right: 0; top: 0; z-index: 1040;' : (show_info_nav ? `width: ${info_nav_width}px; min-width: ${info_nav_width}px; max-width: ${info_nav_width}px; margin-left: -${info_nav_width}px; z-index: 1040;` : 'margin-left: 0px; z-index: 1040;')"
194
223
  x-show="has_content($refs.full_page_info_navigation) && show_info_nav"
195
- x-transition:enter="side-panel-transition-enter"
196
- x-transition:enter-end="side-panel-transition-enter-end"
197
- x-transition:enter-start="side-panel-transition-enter-start-right"
198
- x-transition:leave="side-panel-transition-leave"
199
- x-transition:leave-end="side-panel-transition-leave-end-right"
200
- x-transition:leave-start="side-panel-transition-leave-start"
224
+ x-transition
201
225
  >
202
226
  <div
203
- :style="is_mobile ? 'height: 100vh;' : ''"
227
+ :style="(is_mobile && is_narrow_viewport) ? 'height: 100vh;' : ''"
204
228
  class="row sticky-top border-start border-app-primary side-panel overflow-y-auto bg-app-layer-one"
205
229
  >
206
230
  <div class="col">
@@ -216,18 +240,16 @@
216
240
  </div>
217
241
 
218
242
  <div class="col-auto px-1">
219
- <button
220
- @click="show_info_nav = false"
221
- class="btn-close-panel"
222
- type="button"
223
- >
224
- <i class="bi bi-x-lg"></i>
225
- </button>
243
+ {% include 'django_spire/button/primary_button.html' with button_icon='bi bi-x-lg' x_button_click="close_info()" %}
226
244
  </div>
227
245
  </div>
228
246
 
229
247
  <div class="row">
230
- <div class="col" x-ref="full_page_info_navigation">
248
+ <div
249
+ x-on:click.capture="if (is_narrow_viewport) { $nextTick(() => { show_info_nav = false }) }"
250
+ class="col"
251
+ x-ref="full_page_info_navigation"
252
+ >
231
253
  {% block full_page_info_navigation %}
232
254
  {% endblock %}
233
255
  </div>
@@ -23,8 +23,8 @@ class CollectionSeeder(DjangoModelSeeder):
23
23
 
24
24
  @classmethod
25
25
  def _correct_order(
26
- cls,
27
- child_collections: list[models.Collection]
26
+ cls,
27
+ child_collections: list[models.Collection]
28
28
  ) -> list[models.Collection]:
29
29
  parent_collections = cls.model_class.objects.parentless()
30
30
 
@@ -34,9 +34,9 @@ class EntrySeeder(DjangoModelSeeder):
34
34
 
35
35
  @classmethod
36
36
  def seed_database(
37
- cls,
38
- count: int = 1,
39
- fields: dict | None = None
37
+ cls,
38
+ count: int = 1,
39
+ fields: dict | None = None
40
40
  ) -> list[models.Entry]:
41
41
  entries = super().seed_database(
42
42
  count=count,
@@ -44,13 +44,18 @@ class EntrySeeder(DjangoModelSeeder):
44
44
  )
45
45
 
46
46
  cls._correct_order(entries)
47
- return cls._set_current_version(entries=entries, count=count)
47
+ entries = cls._set_current_version(entries=entries, count=count)
48
+
49
+ for entry in entries:
50
+ entry.services.tag.process_and_set_tags()
51
+
52
+ return entries
48
53
 
49
54
  @classmethod
50
55
  def _set_current_version(cls, entries: list[models.Entry], count: int = 1):
51
56
  entry_versions = EntryVersionSeeder.seed_database(count=count)
52
57
 
53
- for entry, entry_version in zip(entries, entry_versions):
58
+ for entry, entry_version in zip(entries, entry_versions, strict=False):
54
59
  entry_version.entry = entry
55
60
  entry.current_version = entry_version
56
61
 
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from dandy import Decoder
4
6
 
5
- from django_spire.knowledge.collection.models import Collection
7
+ if TYPE_CHECKING:
8
+ from django_spire.knowledge.collection.models import Collection
6
9
 
7
10
 
8
11
  def get_entry_decoder(collection: Collection) -> Decoder:
@@ -9,5 +9,5 @@ class KnowledgeMessageIntel(BaseMessageIntel):
9
9
  body: str
10
10
  entries_intel: EntriesIntel
11
11
 
12
- def content_to_str(self) -> str:
12
+ def render_to_str(self) -> str:
13
13
  return self.body
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django_spire.ai.chat.router import BaseChatRouter
6
+ from django_spire.knowledge.intelligence.workflows.knowledge_workflow import knowledge_search_workflow
7
+
8
+ if TYPE_CHECKING:
9
+ from dandy.llm.request.message import MessageHistory
10
+ from django.core.handlers.wsgi import WSGIRequest
11
+
12
+ from django_spire.ai.chat.message_intel import BaseMessageIntel
13
+
14
+
15
+ class KnowledgeSearchRouter(BaseChatRouter):
16
+ def workflow(
17
+ self,
18
+ request: WSGIRequest,
19
+ user_input: str,
20
+ message_history: MessageHistory | None = None
21
+ ) -> BaseMessageIntel:
22
+ return knowledge_search_workflow(
23
+ request=request,
24
+ user_input=user_input,
25
+ message_history=message_history
26
+ )
@@ -15,15 +15,16 @@ if TYPE_CHECKING:
15
15
  from django.core.handlers.wsgi import WSGIRequest
16
16
  from dandy.llm.request.message import MessageHistory
17
17
 
18
+
18
19
  NO_KNOWLEDGE_MESSAGE_INTEL = DefaultMessageIntel(
19
20
  text='Sorry, I could not find any information on that.'
20
21
  )
21
22
 
22
23
 
23
24
  def knowledge_search_workflow(
24
- request: WSGIRequest,
25
- user_input: str,
26
- message_history: MessageHistory,
25
+ request: WSGIRequest,
26
+ user_input: str,
27
+ message_history: MessageHistory | None = None,
27
28
  ) -> BaseMessageIntel | None:
28
29
  collection_decoder = get_collection_decoder()
29
30
  collections = collection_decoder.process(user_input).values
@@ -7,6 +7,7 @@
7
7
  {% if AuthController.knowledge.can_view %}
8
8
  :href="'{% url "django_spire:knowledge:entry:version:page:editor" pk=0 %}'.replace(0, entry.version_id)"
9
9
  {% endif %}
10
+ data-closes-nav
10
11
  >
11
12
  <i class="bi bi-file-earmark-text flex-shrink-0"></i>
12
13
  <span class="text-truncate ms-1" x-text="entry.name"></span>
django_spire/settings.py CHANGED
@@ -5,13 +5,20 @@ DJANGO_SPIRE_AUTH_CONTROLLERS = {
5
5
  }
6
6
 
7
7
  # AI Settings
8
- AI_PERSONA_NAME = 'AI Assistant'
9
- AI_CHAT_CALLABLE = None
10
- AI_CHAT_DEFAULT_CALLABLE = None
11
- AI_SMS_CONVERSATION_DEFAULT_CALLABLE = None
8
+ DJANGO_SPIRE_AI_PERSONA_NAME = 'AI Assistant'
9
+ DJANGO_SPIRE_AI_DEFAULT_CHAT_ROUTER = 'SPIRE'
12
10
 
13
- ORGANIZATION_NAME = 'No Organization Provided'
14
- ORGANIZATION_DESCRIPTION = 'No Description Provided'
11
+ DJANGO_SPIRE_AI_CHAT_ROUTERS = {
12
+ 'SPIRE': 'django_spire.ai.chat.router.SpireChatRouter',
13
+ }
14
+
15
+ DJANGO_SPIRE_AI_INTENT_CHAT_ROUTERS = {
16
+ 'KNOWLEDGE_SEARCH': {
17
+ 'INTENT_DESCRIPTION': 'The user is asking about information in the knowledge base.',
18
+ 'REQUIRED_PERMISSION': 'django_spire_knowledge.view_collection',
19
+ 'CHAT_ROUTER': 'django_spire.knowledge.intelligence.router.KnowledgeSearchRouter',
20
+ },
21
+ }
15
22
 
16
23
  # Theme Settings
17
24
  DJANGO_SPIRE_DEFAULT_THEME = 'default-light'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.20.4
3
+ Version: 0.21.1
4
4
  Summary: A project for Django Spire
5
5
  Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
6
6
  License: Copyright (c) 2024 Stratus Advanced Technologies and Contributors.