django-spire 0.21.0__py3-none-any.whl → 0.22.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.
- django_spire/ai/chat/templates/django_spire/ai/chat/element/recent_chat_select_element.html +1 -1
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/selection_widget.html +1 -1
- django_spire/consts.py +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +4 -4
- django_spire/core/static/django_spire/css/app-side-panel.css +0 -45
- django_spire/core/{tags → tag}/intelligence/tag_set_bot.py +5 -4
- django_spire/core/tag/mixins.py +23 -0
- django_spire/core/{tags → tag}/models.py +1 -1
- django_spire/core/tag/service/tag_service.py +72 -0
- django_spire/core/{tags → tag}/tests/test_intelligence.py +1 -1
- django_spire/core/tag/tests/test_tags.py +102 -0
- django_spire/core/tag/tools.py +66 -0
- django_spire/core/templates/django_spire/navigation/top_navigation.html +42 -38
- django_spire/core/templates/django_spire/page/full_page.html +69 -47
- django_spire/core/templates/django_spire/tag/element/tag.html +1 -0
- django_spire/core/templatetags/spire_core_tags.py +12 -0
- django_spire/knowledge/collection/models.py +2 -2
- django_spire/knowledge/collection/services/tag_service.py +16 -14
- django_spire/knowledge/entry/models.py +2 -2
- django_spire/knowledge/entry/querysets.py +5 -0
- django_spire/knowledge/entry/services/tag_service.py +5 -5
- django_spire/knowledge/intelligence/bots/entries_search_llm_bot.py +44 -0
- django_spire/knowledge/intelligence/intel/entry_intel.py +24 -4
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +46 -25
- django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html +6 -4
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +3 -2
- django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +14 -6
- django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/entry_sub_navigation_item.html +1 -0
- django_spire/settings.py +1 -1
- {django_spire-0.21.0.dist-info → django_spire-0.22.0.dist-info}/METADATA +2 -2
- {django_spire-0.21.0.dist-info → django_spire-0.22.0.dist-info}/RECORD +39 -41
- django_spire/core/tags/mixins.py +0 -61
- django_spire/core/tags/tests/test_tags.py +0 -102
- django_spire/core/tags/tools.py +0 -20
- django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +0 -45
- django_spire/knowledge/intelligence/decoders/collection_decoder.py +0 -19
- django_spire/knowledge/intelligence/decoders/entry_decoder.py +0 -22
- django_spire/knowledge/intelligence/intel/collection_intel.py +0 -8
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/form_page.html +0 -26
- /django_spire/core/{tags → tag}/__init__.py +0 -0
- /django_spire/core/{tags → tag}/intelligence/__init__.py +0 -0
- /django_spire/core/{tags → tag}/querysets.py +0 -0
- /django_spire/core/{tags/tests → tag/service}/__init__.py +0 -0
- /django_spire/{knowledge/intelligence/decoders → core/tag/tests}/__init__.py +0 -0
- {django_spire-0.21.0.dist-info → django_spire-0.22.0.dist-info}/WHEEL +0 -0
- {django_spire-0.21.0.dist-info → django_spire-0.22.0.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.21.0.dist-info → django_spire-0.22.0.dist-info}/top_level.txt +0 -0
|
@@ -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:
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<span class="badge rounded-pill fw-normal border border-app-primary text-app-primary">{{ tag|title }} {{ weight|default:1 }}</span>
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import random
|
|
4
4
|
import string
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from typing_extensions import Sequence, TYPE_CHECKING, TypeVar
|
|
7
8
|
|
|
@@ -48,6 +49,17 @@ def content_type_url(url_name: str, obj: T, **kwargs) -> str:
|
|
|
48
49
|
return reverse(url_name, kwargs=kwargs)
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
@register.filter
|
|
53
|
+
def safe_dict_items(dictionary: dict) -> Any:
|
|
54
|
+
"""
|
|
55
|
+
Explicitly call .items() on a dictionary, bypassing key lookup.
|
|
56
|
+
Use when the dict has a key named 'items' and you need the method.
|
|
57
|
+
"""
|
|
58
|
+
if hasattr(dictionary, 'items'):
|
|
59
|
+
return dictionary.items()
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
|
|
51
63
|
@register.filter
|
|
52
64
|
def in_list(value: str, arg: str) -> bool:
|
|
53
65
|
"""
|
|
@@ -5,7 +5,7 @@ from django.db import models
|
|
|
5
5
|
from django_spire.auth.group.models import AuthGroup
|
|
6
6
|
from django_spire.contrib import Breadcrumbs
|
|
7
7
|
from django_spire.contrib.ordering.mixins import OrderingModelMixin
|
|
8
|
-
from django_spire.core.
|
|
8
|
+
from django_spire.core.tag.mixins import TagModelMixin
|
|
9
9
|
from django_spire.contrib.utils import truncate_string
|
|
10
10
|
from django_spire.history.mixins import HistoryModelMixin
|
|
11
11
|
from django_spire.knowledge.collection.querysets import CollectionQuerySet
|
|
@@ -16,7 +16,7 @@ from django_spire.knowledge.collection.services.service import CollectionGroupSe
|
|
|
16
16
|
class Collection(
|
|
17
17
|
HistoryModelMixin,
|
|
18
18
|
OrderingModelMixin,
|
|
19
|
-
|
|
19
|
+
TagModelMixin,
|
|
20
20
|
):
|
|
21
21
|
parent = models.ForeignKey(
|
|
22
22
|
'self',
|
|
@@ -4,14 +4,16 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from dandy import Prompt
|
|
6
6
|
|
|
7
|
-
from django_spire.
|
|
8
|
-
from django_spire.core.
|
|
7
|
+
from django_spire.core.tag.intelligence.tag_set_bot import TagSetBot
|
|
8
|
+
from django_spire.core.tag.service.tag_service import BaseTagService
|
|
9
|
+
from django_spire.core.tag.tools import simplify_tag_set, simplify_and_weight_tag_set_to_dict, \
|
|
10
|
+
get_score_percentage_from_tag_set_weighted
|
|
9
11
|
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from django_spire.knowledge.collection.models import Collection
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class CollectionTagService(
|
|
16
|
+
class CollectionTagService(BaseTagService['Collection']):
|
|
15
17
|
obj: Collection
|
|
16
18
|
|
|
17
19
|
def process_and_set_tags(self):
|
|
@@ -24,11 +26,11 @@ class CollectionTagService(BaseDjangoModelService['Collection']):
|
|
|
24
26
|
content=collection_prompt
|
|
25
27
|
)
|
|
26
28
|
|
|
27
|
-
self.
|
|
29
|
+
self.set_tags_from_tag_set(
|
|
28
30
|
tag_set=tag_set,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
|
-
def get_aggregated_tag_set(self):
|
|
33
|
+
def get_aggregated_tag_set(self) -> set[str]:
|
|
32
34
|
tag_set = self.obj.tag_set
|
|
33
35
|
|
|
34
36
|
for collection in self.obj.children.active():
|
|
@@ -39,14 +41,14 @@ class CollectionTagService(BaseDjangoModelService['Collection']):
|
|
|
39
41
|
|
|
40
42
|
return tag_set
|
|
41
43
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for entry in self.obj.entries.active():
|
|
49
|
-
tag_set.update(entry.tag_set_simplified)
|
|
44
|
+
def get_score_percentage_from_aggregated_tag_set_weighted(self, tag_set: set[str]) -> float:
|
|
45
|
+
return get_score_percentage_from_tag_set_weighted(
|
|
46
|
+
tag_set_actual=tag_set,
|
|
47
|
+
tag_set_reference=self.get_aggregated_tag_set()
|
|
48
|
+
)
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
def get_simplified_aggregated_tag_set(self) -> set[str]:
|
|
51
|
+
return simplify_tag_set(self.get_aggregated_tag_set())
|
|
52
52
|
|
|
53
|
+
def get_simplified_and_weighted_aggregated_tag_set(self) -> dict[str, int]:
|
|
54
|
+
return simplify_and_weight_tag_set_to_dict(self.get_aggregated_tag_set())
|
|
@@ -3,7 +3,7 @@ from django.urls import reverse
|
|
|
3
3
|
|
|
4
4
|
from django_spire.contrib import Breadcrumbs
|
|
5
5
|
from django_spire.contrib.ordering.mixins import OrderingModelMixin
|
|
6
|
-
from django_spire.core.
|
|
6
|
+
from django_spire.core.tag.mixins import TagModelMixin
|
|
7
7
|
from django_spire.contrib.utils import truncate_string
|
|
8
8
|
from django_spire.history.mixins import HistoryModelMixin
|
|
9
9
|
from django_spire.knowledge.collection.models import Collection
|
|
@@ -15,7 +15,7 @@ from django_spire.knowledge.entry.version.models import EntryVersion
|
|
|
15
15
|
class Entry(
|
|
16
16
|
HistoryModelMixin,
|
|
17
17
|
OrderingModelMixin,
|
|
18
|
-
|
|
18
|
+
TagModelMixin
|
|
19
19
|
):
|
|
20
20
|
collection = models.ForeignKey(
|
|
21
21
|
Collection,
|
|
@@ -30,3 +30,8 @@ class EntryQuerySet(HistoryQuerySet, OrderingQuerySetMixin):
|
|
|
30
30
|
current_version__status=EntryVersionStatusChoices.DRAFT
|
|
31
31
|
)
|
|
32
32
|
)
|
|
33
|
+
|
|
34
|
+
def get_by_version_block_id(self, version_block_id: int) -> Entry:
|
|
35
|
+
return self.get(
|
|
36
|
+
current_version__block__id=version_block_id
|
|
37
|
+
)
|
|
@@ -4,21 +4,21 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from dandy import Prompt
|
|
6
6
|
|
|
7
|
-
from django_spire.
|
|
8
|
-
from django_spire.core.
|
|
7
|
+
from django_spire.core.tag.intelligence.tag_set_bot import TagSetBot
|
|
8
|
+
from django_spire.core.tag.service.tag_service import BaseTagService
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from django_spire.knowledge.entry.models import Entry
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class EntryTagService(
|
|
15
|
+
class EntryTagService(BaseTagService['Entry']):
|
|
16
16
|
obj: Entry
|
|
17
17
|
|
|
18
18
|
def process_and_set_tags(self):
|
|
19
19
|
entry_prompt = Prompt()
|
|
20
20
|
|
|
21
|
-
entry_prompt.
|
|
21
|
+
entry_prompt.sub_heading(self.obj.name)
|
|
22
22
|
|
|
23
23
|
for version_block in self.obj.current_version.blocks.all():
|
|
24
24
|
entry_prompt.text(f'{version_block.render_to_text()}')
|
|
@@ -27,7 +27,7 @@ class EntryTagService(BaseDjangoModelService['Entry']):
|
|
|
27
27
|
content=entry_prompt
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
self.
|
|
30
|
+
self.set_tags_from_tag_set(
|
|
31
31
|
tag_set=tag_set,
|
|
32
32
|
)
|
|
33
33
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from dandy import Bot, Prompt
|
|
5
|
+
|
|
6
|
+
from django_spire.knowledge.intelligence.intel.entry_intel import EntriesIntel
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from django_spire.knowledge.entry.models import Entry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EntriesSearchBot(Bot):
|
|
13
|
+
llm_role = 'Knowledge Entry Search Assistant'
|
|
14
|
+
llm_task = 'Read through the knowledge and return information and the block id that relevant to the information request.'
|
|
15
|
+
llm_guidelines = (
|
|
16
|
+
Prompt()
|
|
17
|
+
.list([
|
|
18
|
+
'Please read through all the blocks and return 2 of the most relevant ones.',
|
|
19
|
+
'You can add any of the text in the knowledge entries to the 2 responses if it helps.',
|
|
20
|
+
'Make sure the relevant heading text is from a heading with mark down formatting.',
|
|
21
|
+
'When returning the relevant heading remove any of the markdown formating characters.',
|
|
22
|
+
])
|
|
23
|
+
)
|
|
24
|
+
llm_intel_class = EntriesIntel
|
|
25
|
+
|
|
26
|
+
def process(self, user_input: str, entries: list[Entry]) -> EntriesIntel:
|
|
27
|
+
|
|
28
|
+
entry_prompt = Prompt()
|
|
29
|
+
entry_prompt.sub_heading('Information Request')
|
|
30
|
+
entry_prompt.line_break()
|
|
31
|
+
entry_prompt.text(f'{user_input}')
|
|
32
|
+
entry_prompt.line_break()
|
|
33
|
+
entry_prompt.sub_heading('Knowledge Entries')
|
|
34
|
+
entry_prompt.line_break()
|
|
35
|
+
|
|
36
|
+
for entry in entries:
|
|
37
|
+
for version_block in entry.current_version.blocks.all():
|
|
38
|
+
if version_block.render_to_text() != '\n':
|
|
39
|
+
entry_prompt.text(f'{version_block.id}: {version_block.render_to_text()}')
|
|
40
|
+
|
|
41
|
+
return self.llm.prompt_to_intel(
|
|
42
|
+
prompt=entry_prompt,
|
|
43
|
+
)
|
|
44
|
+
|
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
3
5
|
from dandy import BaseIntel, BaseListIntel
|
|
4
6
|
|
|
5
|
-
from django_spire.knowledge.
|
|
7
|
+
from django_spire.knowledge.entry.models import Entry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EntryRelevancy(str, Enum):
|
|
11
|
+
EXTREMELY = 'Extremely'
|
|
12
|
+
VERY = 'Very'
|
|
13
|
+
SOMEWHAT = 'Somewhat'
|
|
14
|
+
NOT_SO_MUCH = 'Not so much'
|
|
15
|
+
NO_RELEVANCE = 'No Relevance'
|
|
6
16
|
|
|
7
17
|
|
|
8
18
|
class EntryIntel(BaseIntel):
|
|
9
19
|
relevant_heading_text: str
|
|
10
|
-
|
|
20
|
+
relevant_text: str
|
|
11
21
|
relevant_block_id: int
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
relevancy: EntryRelevancy
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
return self.relevant_heading_text + ' ' + self.relevant_text
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def collection(self):
|
|
29
|
+
return self.entry.collection
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def entry(self):
|
|
33
|
+
return Entry.objects.get_by_version_block_id(self.relevant_block_id)
|
|
14
34
|
|
|
15
35
|
|
|
16
36
|
class EntriesIntel(BaseListIntel[EntryIntel]):
|
|
@@ -5,51 +5,72 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from django.core.handlers.wsgi import WSGIRequest
|
|
6
6
|
|
|
7
7
|
from django_spire.ai.chat.message_intel import DefaultMessageIntel, BaseMessageIntel
|
|
8
|
-
from django_spire.
|
|
9
|
-
from django_spire.knowledge.
|
|
8
|
+
from django_spire.core.tag.intelligence.tag_set_bot import TagSetBot
|
|
9
|
+
from django_spire.knowledge.collection.models import Collection
|
|
10
|
+
from django_spire.knowledge.intelligence.bots.entries_search_llm_bot import EntriesSearchBot
|
|
10
11
|
from django_spire.knowledge.intelligence.intel.message_intel import KnowledgeMessageIntel
|
|
11
|
-
from django_spire.knowledge.intelligence.decoders.collection_decoder import get_collection_decoder
|
|
12
|
-
from django_spire.knowledge.intelligence.decoders.entry_decoder import get_entry_decoder
|
|
13
12
|
|
|
14
13
|
if TYPE_CHECKING:
|
|
15
14
|
from django.core.handlers.wsgi import WSGIRequest
|
|
16
15
|
from dandy.llm.request.message import MessageHistory
|
|
17
16
|
|
|
18
|
-
|
|
19
17
|
NO_KNOWLEDGE_MESSAGE_INTEL = DefaultMessageIntel(
|
|
20
18
|
text='Sorry, I could not find any information on that.'
|
|
21
19
|
)
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
def knowledge_search_workflow(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
request: WSGIRequest,
|
|
24
|
+
user_input: str,
|
|
25
|
+
message_history: MessageHistory | None = None,
|
|
28
26
|
) -> BaseMessageIntel | None:
|
|
29
|
-
|
|
30
|
-
collections = collection_decoder.process(user_input).values
|
|
27
|
+
user_tag_set = TagSetBot().process(user_input)
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
def get_top_scored_from_dict_to_list(
|
|
30
|
+
scored_dict: dict[str, float],
|
|
31
|
+
score_floor: float = 0.05
|
|
32
|
+
) -> list:
|
|
33
|
+
if not scored_dict:
|
|
34
|
+
return []
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
min_score = min(scored_dict.values())
|
|
37
|
+
max_score = max(scored_dict.values())
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
entry_decoder = get_entry_decoder(collection=collection)
|
|
39
|
+
if min_score == 0 and max_score == 0:
|
|
40
|
+
return []
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
median_score = (max_score - min_score) / 2
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
top_scored_list = []
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
for key, value in scored_dict.items():
|
|
47
|
+
if value >= score_floor and value >= median_score:
|
|
48
|
+
top_scored_list.append(key)
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
return top_scored_list
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
collections = get_top_scored_from_dict_to_list({
|
|
53
|
+
collection: collection.services.tag.get_score_percentage_from_aggregated_tag_set_weighted(
|
|
54
|
+
user_tag_set
|
|
53
55
|
)
|
|
56
|
+
for collection in Collection.objects.all().annotate_entry_count()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
entries = get_top_scored_from_dict_to_list({
|
|
60
|
+
entry: entry.services.tag.get_score_percentage_from_tag_set_weighted(user_tag_set)
|
|
61
|
+
for collection in collections
|
|
62
|
+
for entry in collection.entries.all()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if not entries:
|
|
66
|
+
return NO_KNOWLEDGE_MESSAGE_INTEL
|
|
67
|
+
|
|
68
|
+
entries_intel = EntriesSearchBot(llm_temperature=0.0).process(
|
|
69
|
+
user_input=user_input,
|
|
70
|
+
entries=entries
|
|
71
|
+
)
|
|
54
72
|
|
|
55
|
-
return KnowledgeMessageIntel(
|
|
73
|
+
return KnowledgeMessageIntel(
|
|
74
|
+
body=f'{" ".join([str(entry) for entry in entries_intel])}',
|
|
75
|
+
entries_intel=entries_intel,
|
|
76
|
+
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{% extends 'django_spire/knowledge/page/full_page.html' %}
|
|
2
2
|
|
|
3
|
+
{% load spire_core_tags %}
|
|
4
|
+
|
|
3
5
|
{% block full_page_sub_navigation_title %}
|
|
4
6
|
{{ collection.name_short }}
|
|
5
7
|
{% endblock %}
|
|
@@ -49,16 +51,16 @@
|
|
|
49
51
|
<div class="row">
|
|
50
52
|
<div class="col-12 col-lg-8 mx-auto pb-4">
|
|
51
53
|
Tags:
|
|
52
|
-
{% for tag in collection.
|
|
53
|
-
|
|
54
|
+
{% for tag, weight in collection.simplified_and_weighted_tag_dict|safe_dict_items %}
|
|
55
|
+
{% include 'django_spire/tag/element/tag.html' %}
|
|
54
56
|
{% endfor %}
|
|
55
57
|
</div>
|
|
56
58
|
</div>
|
|
57
59
|
<div class="row">
|
|
58
60
|
<div class="col-12 col-lg-8 mx-auto pb-4">
|
|
59
61
|
Aggregated Tags:
|
|
60
|
-
{% for tag in collection.services.tag.
|
|
61
|
-
|
|
62
|
+
{% for tag, weight in collection.services.tag.get_simplified_and_weighted_aggregated_tag_set|safe_dict_items %}
|
|
63
|
+
{% include 'django_spire/tag/element/tag.html' %}
|
|
62
64
|
{% endfor %}
|
|
63
65
|
</div>
|
|
64
66
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{% extends 'django_spire/knowledge/page/full_page.html' %}
|
|
2
2
|
|
|
3
3
|
{% load static %}
|
|
4
|
+
{% load spire_core_tags %}
|
|
4
5
|
|
|
5
6
|
{% block base_body_additional_top_js %}
|
|
6
7
|
{{ block.super }}
|
|
@@ -46,8 +47,8 @@
|
|
|
46
47
|
<div class="row">
|
|
47
48
|
<div class="col-12 mb-3">
|
|
48
49
|
{% include 'django_spire/element/attribute_element.html' with attribute_title='Tags' %}
|
|
49
|
-
{% for tag in entry.
|
|
50
|
-
|
|
50
|
+
{% for tag, weight in entry.simplified_and_weighted_tag_dict|safe_dict_items %}
|
|
51
|
+
{% include 'django_spire/tag/element/tag.html' %}
|
|
51
52
|
{% endfor %}
|
|
52
53
|
</div>
|
|
53
54
|
</div>
|
django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html
CHANGED
|
@@ -4,19 +4,27 @@
|
|
|
4
4
|
{% for entry_intel in message_intel.entries_intel %}
|
|
5
5
|
|
|
6
6
|
<div class="row">
|
|
7
|
-
<div class="col
|
|
8
|
-
|
|
9
|
-
{{ entry_intel.relevant_heading_text }}
|
|
10
|
-
</a>
|
|
7
|
+
<div class="col">
|
|
8
|
+
{{ entry_intel.relevant_text }}
|
|
11
9
|
</div>
|
|
12
10
|
</div>
|
|
13
11
|
|
|
14
12
|
<div class="row">
|
|
15
|
-
<div class="col pb-
|
|
16
|
-
{{ entry_intel.
|
|
13
|
+
<div class="col pt-1 pb-3 fs-7">
|
|
14
|
+
<a href="{% url 'django_spire:knowledge:entry:version:page:editor' pk=entry_intel.entry.id %}?show_sub_nav=false&block_id={{ entry_intel.relevant_block_id }}"
|
|
15
|
+
target="_blank">
|
|
16
|
+
{{ entry_intel.relevant_heading_text }}
|
|
17
|
+
</a>
|
|
17
18
|
</div>
|
|
18
19
|
</div>
|
|
19
20
|
|
|
20
21
|
{% endfor %}
|
|
21
22
|
|
|
23
|
+
<div class="row">
|
|
24
|
+
<div class="col">
|
|
25
|
+
Is there anything else I can help with?
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
|
|
22
30
|
{% endblock %}
|