django-spire 0.19.5__py3-none-any.whl → 0.20.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.
- django_spire/ai/chat/intelligence/decoders/tools.py +7 -2
- django_spire/ai/chat/intelligence/workflows/chat_workflow.py +22 -22
- django_spire/consts.py +1 -1
- django_spire/core/migrations/0001_initial.py +26 -0
- django_spire/core/migrations/__init__.py +0 -0
- django_spire/core/tags/__init__.py +0 -0
- django_spire/core/tags/intelligence/__init__.py +0 -0
- django_spire/core/tags/intelligence/tag_set_bot.py +41 -0
- django_spire/core/tags/mixins.py +61 -0
- django_spire/core/tags/models.py +38 -0
- django_spire/core/tags/querysets.py +9 -0
- django_spire/core/tags/tests/__init__.py +0 -0
- django_spire/core/tags/tests/test_intelligence.py +28 -0
- django_spire/core/tags/tests/test_tags.py +102 -0
- django_spire/core/tags/tools.py +20 -0
- django_spire/knowledge/collection/admin.py +20 -1
- django_spire/knowledge/collection/models.py +6 -1
- django_spire/knowledge/collection/services/service.py +2 -0
- django_spire/knowledge/collection/services/tag_service.py +52 -0
- django_spire/knowledge/collection/views/form_views.py +2 -0
- django_spire/knowledge/entry/admin.py +18 -2
- django_spire/knowledge/entry/models.py +6 -1
- django_spire/knowledge/entry/services/service.py +2 -0
- django_spire/knowledge/entry/services/tag_service.py +34 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +5 -1
- django_spire/knowledge/entry/version/views/json_views.py +9 -0
- django_spire/knowledge/intelligence/decoders/collection_decoder.py +4 -4
- django_spire/knowledge/intelligence/decoders/entry_decoder.py +2 -2
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +9 -3
- django_spire/knowledge/migrations/0008_collection_tags_entry_tags.py +24 -0
- django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html +16 -0
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/container/{detail_container.html → editor_container.html} +6 -0
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +27 -10
- django_spire/settings.py +1 -0
- {django_spire-0.19.5.dist-info → django_spire-0.20.1.dist-info}/METADATA +2 -2
- {django_spire-0.19.5.dist-info → django_spire-0.20.1.dist-info}/RECORD +39 -24
- {django_spire-0.19.5.dist-info → django_spire-0.20.1.dist-info}/WHEEL +0 -0
- {django_spire-0.19.5.dist-info → django_spire-0.20.1.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.19.5.dist-info → django_spire-0.20.1.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,8 @@ from typing import Callable, TYPE_CHECKING
|
|
|
5
5
|
from dandy import Decoder
|
|
6
6
|
|
|
7
7
|
from django_spire.auth.controller.controller import AppAuthController
|
|
8
|
+
from django_spire.conf import settings
|
|
9
|
+
from django_spire.core.utils import get_callable_from_module_string_and_validate_arguments
|
|
8
10
|
from django_spire.knowledge.intelligence.workflows.knowledge_workflow import knowledge_search_workflow
|
|
9
11
|
|
|
10
12
|
if TYPE_CHECKING:
|
|
@@ -20,8 +22,11 @@ def generate_intent_decoder(
|
|
|
20
22
|
if AppAuthController(app_name='knowledge', request=request).can_view():
|
|
21
23
|
intent_dict['The user is looking for information or knowledge on something.'] = knowledge_search_workflow
|
|
22
24
|
|
|
23
|
-
if
|
|
24
|
-
intent_dict['None of the above choices match the user\'s intent'] =
|
|
25
|
+
if settings.AI_CHAT_DEFAULT_CALLABLE is not None:
|
|
26
|
+
intent_dict['None of the above choices match the user\'s intent'] = get_callable_from_module_string_and_validate_arguments(
|
|
27
|
+
settings.AI_CHAT_DEFAULT_CALLABLE,
|
|
28
|
+
['request', 'user_input', 'message_history']
|
|
29
|
+
)
|
|
25
30
|
|
|
26
31
|
return Decoder(
|
|
27
32
|
mapping_keys_description='Intent of the User\'s Request',
|
|
@@ -8,25 +8,15 @@ from dandy.recorder import recorder_to_html_file
|
|
|
8
8
|
from django_spire.ai.chat.intelligence.decoders.tools import generate_intent_decoder
|
|
9
9
|
from django_spire.ai.chat.message_intel import BaseMessageIntel, DefaultMessageIntel
|
|
10
10
|
from django_spire.ai.decorators import log_ai_interaction_from_recorder
|
|
11
|
+
from django_spire.conf import settings
|
|
12
|
+
from django_spire.core.utils import get_callable_from_module_string_and_validate_arguments
|
|
11
13
|
|
|
12
14
|
if TYPE_CHECKING:
|
|
13
15
|
from dandy.llm.request.message import MessageHistory
|
|
14
16
|
from django.core.handlers.wsgi import WSGIRequest
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
def
|
|
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(
|
|
19
|
+
def default_chat_callable(
|
|
30
20
|
request: WSGIRequest,
|
|
31
21
|
user_input: str,
|
|
32
22
|
message_history: MessageHistory | None = None
|
|
@@ -45,13 +35,6 @@ def chat_workflow(
|
|
|
45
35
|
user_input: str,
|
|
46
36
|
message_history: MessageHistory | None = None
|
|
47
37
|
) -> 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
38
|
@log_ai_interaction_from_recorder(request.user)
|
|
56
39
|
def run_workflow_process(callable_: Callable) -> BaseMessageIntel | None:
|
|
57
40
|
return callable_(
|
|
@@ -60,11 +43,28 @@ def chat_workflow(
|
|
|
60
43
|
message_history=message_history,
|
|
61
44
|
)
|
|
62
45
|
|
|
63
|
-
|
|
46
|
+
if settings.AI_CHAT_CALLABLE is not None:
|
|
47
|
+
chat_callable = get_callable_from_module_string_and_validate_arguments(
|
|
48
|
+
settings.AI_CHAT_CALLABLE,
|
|
49
|
+
['request', 'user_input', 'message_history']
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
message_intel = run_workflow_process(chat_callable)
|
|
53
|
+
|
|
54
|
+
else:
|
|
55
|
+
|
|
56
|
+
intent_decoder = generate_intent_decoder(
|
|
57
|
+
request=request,
|
|
58
|
+
default_callable=default_chat_callable,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
intent_process = intent_decoder.process(user_input, max_return_values=1)[0]
|
|
62
|
+
|
|
63
|
+
message_intel = run_workflow_process(intent_process)
|
|
64
64
|
|
|
65
65
|
if not isinstance(message_intel, BaseMessageIntel):
|
|
66
66
|
if message_intel is None:
|
|
67
|
-
return
|
|
67
|
+
return default_chat_callable(
|
|
68
68
|
request=request,
|
|
69
69
|
user_input=user_input,
|
|
70
70
|
message_history=message_history
|
django_spire/consts.py
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-11-09 05:02
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
initial = True
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name='Tag',
|
|
16
|
+
fields=[
|
|
17
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
18
|
+
('name', models.SlugField(max_length=128, unique=True)),
|
|
19
|
+
],
|
|
20
|
+
options={
|
|
21
|
+
'verbose_name': 'Tag',
|
|
22
|
+
'verbose_name_plural': 'Tags',
|
|
23
|
+
'db_table': 'django_spire_core_tag',
|
|
24
|
+
},
|
|
25
|
+
),
|
|
26
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from dandy import Bot, Prompt, BaseListIntel
|
|
2
|
+
from django.utils.text import slugify
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TagsIntel(BaseListIntel[list[str]]):
|
|
6
|
+
tags: list[str]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TagSetBot(Bot):
|
|
10
|
+
llm_role = 'Tag Identifier'
|
|
11
|
+
llm_task = 'Read through the provided content and return a list of tags.'
|
|
12
|
+
llm_guidelines = (
|
|
13
|
+
Prompt()
|
|
14
|
+
.list([
|
|
15
|
+
'Make sure to have enough tags to properly cover all the provided content.',
|
|
16
|
+
'Focus on tagging the words in the content',
|
|
17
|
+
'Only add additional words that are very relevant to the content.'
|
|
18
|
+
'Use spaces to separate words in tags',
|
|
19
|
+
'Include known acronyms along with the full tags.',
|
|
20
|
+
])
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def process(self, content: Prompt | str) -> set[str]:
|
|
24
|
+
tags_intel = self.llm.prompt_to_intel(
|
|
25
|
+
prompt=(
|
|
26
|
+
Prompt()
|
|
27
|
+
.heading('Content to be Tagged:')
|
|
28
|
+
.line_break()
|
|
29
|
+
.text(content)
|
|
30
|
+
),
|
|
31
|
+
intel_class=TagsIntel
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
for tag in tags_intel:
|
|
35
|
+
word_segments = tag.split(" ")
|
|
36
|
+
|
|
37
|
+
if len(word_segments) > 1:
|
|
38
|
+
for word_segment in word_segments:
|
|
39
|
+
tags_intel.append(word_segment)
|
|
40
|
+
|
|
41
|
+
return {slugify(tag) for tag in tags_intel}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from django.db.models import QuerySet
|
|
3
|
+
|
|
4
|
+
from django_spire.core.tags.models import Tag
|
|
5
|
+
from django_spire.core.tags import tools
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TagsModelMixin(models.Model):
|
|
9
|
+
tags = models.ManyToManyField(Tag, related_name='+', null=True, blank=True, editable=False)
|
|
10
|
+
|
|
11
|
+
class Meta:
|
|
12
|
+
abstract = True
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def tag_set(self) -> set[str]:
|
|
16
|
+
return set(self.tags.values_list('name', flat=True))
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def tag_set_simplified(self) -> set[str]:
|
|
20
|
+
simplified_tag_words = []
|
|
21
|
+
|
|
22
|
+
for tag in self.tag_set:
|
|
23
|
+
tag_words = tag.split('-')
|
|
24
|
+
simplified_tag_words.extend(tag_words)
|
|
25
|
+
|
|
26
|
+
return set(simplified_tag_words)
|
|
27
|
+
|
|
28
|
+
def add_tags_from_tag_set(self, tag_set: set[str]):
|
|
29
|
+
self._update_global_tags_from_set(tag_set)
|
|
30
|
+
|
|
31
|
+
self.tags.add(*Tag.objects.in_tag_set(tag_set))
|
|
32
|
+
|
|
33
|
+
def clear_tags(self):
|
|
34
|
+
self.tags.clear()
|
|
35
|
+
|
|
36
|
+
def get_matching_tags_from_tag_set(self, tag_set: set[str]) -> QuerySet:
|
|
37
|
+
return self.tags.in_tag_set(tag_set)
|
|
38
|
+
|
|
39
|
+
def get_matching_percentage_of_tag_set(self, tag_set: set[str]) -> float:
|
|
40
|
+
return tools.get_matching_a_percentage_from_tag_sets(tag_set, self.tag_set)
|
|
41
|
+
|
|
42
|
+
def get_matching_percentage_of_model_tags_from_tag_set(self, tag_set: set[str]) -> float:
|
|
43
|
+
return tools.get_matching_b_percentage_from_tag_sets(tag_set, self.tag_set)
|
|
44
|
+
|
|
45
|
+
def has_tags_in_tag_set(self, tag_set: set[str]) -> bool:
|
|
46
|
+
return self.tag_set.issuperset(tag_set)
|
|
47
|
+
|
|
48
|
+
def remove_tags_by_tag_set(self, tag_set: set[str]):
|
|
49
|
+
tag_objects = Tag.objects.in_tag_set(tag_set)
|
|
50
|
+
|
|
51
|
+
self.tags.remove(*tag_objects)
|
|
52
|
+
|
|
53
|
+
def set_tags_from_tag_set(self, tag_set: set[str]):
|
|
54
|
+
self._update_global_tags_from_set(tag_set)
|
|
55
|
+
|
|
56
|
+
tag_objects = Tag.objects.in_tag_set(tag_set)
|
|
57
|
+
self.tags.set(tag_objects)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _update_global_tags_from_set(tag_set: set[str]):
|
|
61
|
+
Tag.add_tags([Tag(name=tag) for tag in tag_set])
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Sequence, Self
|
|
2
|
+
|
|
3
|
+
from django.db import models
|
|
4
|
+
from django.utils.text import slugify
|
|
5
|
+
|
|
6
|
+
from django_spire.core.tags.querysets import TagQuerySet
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Tag(models.Model):
|
|
10
|
+
name = models.SlugField(
|
|
11
|
+
max_length=128,
|
|
12
|
+
unique=True
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
objects = TagQuerySet.as_manager()
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, **kwargs):
|
|
18
|
+
name = kwargs.get('name')
|
|
19
|
+
if name:
|
|
20
|
+
kwargs['name'] = slugify(name)
|
|
21
|
+
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
return self.name
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def add_tags(cls, tags: Sequence[Self]):
|
|
29
|
+
cls.objects.bulk_create(
|
|
30
|
+
tags,
|
|
31
|
+
ignore_conflicts=True
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
class Meta:
|
|
35
|
+
db_table = 'django_spire_core_tag'
|
|
36
|
+
verbose_name = 'Tag'
|
|
37
|
+
verbose_name_plural = 'Tags'
|
|
38
|
+
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tags.intelligence.tag_set_bot import TagSetBot
|
|
4
|
+
|
|
5
|
+
TEST_INPUT = """
|
|
6
|
+
I love reading books about science fiction, fantasy, and adventure, especially ones
|
|
7
|
+
that feature artificial intelligence, machine learning, and natural language processing.
|
|
8
|
+
Some of my favorite authors include Isaac Asimov, J.R.R. Tolkien, and Neil Gaiman.
|
|
9
|
+
I'm also interested in learning more about data science, programming languages like Python
|
|
10
|
+
and Java, and emerging technologies like blockchain and the Internet of Things (IoT).
|
|
11
|
+
Can you recommend some books or resources that align with these interests?
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestTagIntelligence(TestCase):
|
|
16
|
+
def setUp(self):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def test_tag_set_bot(self):
|
|
20
|
+
tag_set = TagSetBot().process(TEST_INPUT)
|
|
21
|
+
|
|
22
|
+
self.assertIn('science', tag_set)
|
|
23
|
+
self.assertIn('artificial', tag_set)
|
|
24
|
+
self.assertIn('fantasy', tag_set)
|
|
25
|
+
|
|
26
|
+
self.assertNotIn('camping', tag_set)
|
|
27
|
+
self.assertNotIn('art', tag_set)
|
|
28
|
+
self.assertNotIn('hate', tag_set)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tags.models import Tag
|
|
4
|
+
from django_spire.knowledge.collection.models import Collection
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestTags(TestCase):
|
|
8
|
+
def setUp(self):
|
|
9
|
+
self.collection = Collection.objects.create(
|
|
10
|
+
name='Test Collection', description='Test Collection Description'
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
self.tag_set_a = {'apple', 'banana', 'orange'}
|
|
14
|
+
self.tag_set_b = {'grape', 'kiwi', 'pear'}
|
|
15
|
+
self.tag_set_c = {'lemon', 'lime', 'mango'}
|
|
16
|
+
|
|
17
|
+
self.mixed_tag_set = {'apple', 'grape', 'lemon'}
|
|
18
|
+
|
|
19
|
+
self.dirty_tag_set = {'HaT~', 'four* tacos', '123 hoop!!'}
|
|
20
|
+
|
|
21
|
+
self.collection.add_tags_from_tag_set(self.tag_set_a)
|
|
22
|
+
self.collection.add_tags_from_tag_set(self.tag_set_b)
|
|
23
|
+
self.collection.add_tags_from_tag_set(self.tag_set_c)
|
|
24
|
+
|
|
25
|
+
self.collection.add_tags_from_tag_set(self.mixed_tag_set)
|
|
26
|
+
|
|
27
|
+
self.collection.add_tags_from_tag_set(self.dirty_tag_set)
|
|
28
|
+
|
|
29
|
+
def test_tag_removal(self):
|
|
30
|
+
self.assertIn('apple', self.collection.tag_set)
|
|
31
|
+
|
|
32
|
+
self.collection.remove_tags_by_tag_set(self.mixed_tag_set)
|
|
33
|
+
|
|
34
|
+
self.assertNotIn('lemon', self.collection.tag_set)
|
|
35
|
+
|
|
36
|
+
self.assertIn('kiwi', self.collection.tag_set)
|
|
37
|
+
|
|
38
|
+
def test_tag_add_and_set(self):
|
|
39
|
+
self.collection.set_tags_from_tag_set(self.mixed_tag_set)
|
|
40
|
+
|
|
41
|
+
self.assertIn('lemon', self.collection.tag_set)
|
|
42
|
+
|
|
43
|
+
self.assertNotIn('banana', self.collection.tag_set)
|
|
44
|
+
|
|
45
|
+
def test_tag_count(self):
|
|
46
|
+
self.assertEqual(Tag.objects.count(), 12)
|
|
47
|
+
self.assertEqual(self.collection.tags.count(), 12)
|
|
48
|
+
|
|
49
|
+
def test_tag_removal(self):
|
|
50
|
+
self.collection.remove_tags_by_tag_set(self.tag_set_a)
|
|
51
|
+
|
|
52
|
+
self.assertEqual(self.collection.tags.count(), 9)
|
|
53
|
+
|
|
54
|
+
def test_tag_bool_methods(self):
|
|
55
|
+
self.collection.remove_tags_by_tag_set(self.tag_set_b)
|
|
56
|
+
|
|
57
|
+
self.assertTrue(self.collection.has_tags_in_tag_set(self.tag_set_c))
|
|
58
|
+
self.assertFalse(self.collection.has_tags_in_tag_set(self.mixed_tag_set))
|
|
59
|
+
|
|
60
|
+
def test_tag_matching_methods(self):
|
|
61
|
+
self.assertEqual(len(self.collection.get_matching_tags_from_tag_set(self.tag_set_a)), 3)
|
|
62
|
+
|
|
63
|
+
self.collection.remove_tags_by_tag_set(self.tag_set_a)
|
|
64
|
+
|
|
65
|
+
self.assertEqual(len(self.collection.get_matching_tags_from_tag_set(self.tag_set_a)), 0)
|
|
66
|
+
self.assertEqual(len(self.collection.get_matching_tags_from_tag_set(self.tag_set_b)), 3)
|
|
67
|
+
self.assertEqual(len(self.collection.get_matching_tags_from_tag_set(self.mixed_tag_set)), 2)
|
|
68
|
+
|
|
69
|
+
self.collection.remove_tags_by_tag_set(self.tag_set_b)
|
|
70
|
+
|
|
71
|
+
self.assertEqual(len(self.collection.get_matching_tags_from_tag_set(self.tag_set_b)), 0)
|
|
72
|
+
|
|
73
|
+
self.assertEqual(len(self.collection.get_matching_tags_from_tag_set(self.mixed_tag_set)), 1)
|
|
74
|
+
|
|
75
|
+
def test_tag_metric_methods(self):
|
|
76
|
+
self.assertAlmostEqual(
|
|
77
|
+
self.collection.get_matching_percentage_of_tag_set(self.tag_set_a),
|
|
78
|
+
1.00,
|
|
79
|
+
2
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.assertAlmostEqual(
|
|
83
|
+
self.collection.get_matching_percentage_of_model_tags_from_tag_set(self.tag_set_b),
|
|
84
|
+
0.25,
|
|
85
|
+
2
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.collection.remove_tags_by_tag_set(self.mixed_tag_set)
|
|
89
|
+
|
|
90
|
+
self.assertAlmostEqual(
|
|
91
|
+
self.collection.get_matching_percentage_of_tag_set(self.tag_set_a),
|
|
92
|
+
0.666,
|
|
93
|
+
2
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
self.assertAlmostEqual(
|
|
98
|
+
self.collection.get_matching_percentage_of_model_tags_from_tag_set(self.tag_set_b),
|
|
99
|
+
0.222,
|
|
100
|
+
2
|
|
101
|
+
)
|
|
102
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
def get_matching_tag_set_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> set[str]:
|
|
2
|
+
return tag_set_a.intersection(tag_set_b)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_matching_count_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> int:
|
|
6
|
+
return len(get_matching_tag_set_from_tag_sets(tag_set_a, tag_set_b))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_matching_a_percentage_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> float:
|
|
10
|
+
try:
|
|
11
|
+
return get_matching_count_from_tag_sets(tag_set_a, tag_set_b) / len(tag_set_a)
|
|
12
|
+
except ZeroDivisionError:
|
|
13
|
+
return 0.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_matching_b_percentage_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> float:
|
|
17
|
+
try:
|
|
18
|
+
return get_matching_count_from_tag_sets(tag_set_a, tag_set_b) / len(tag_set_b)
|
|
19
|
+
except ZeroDivisionError:
|
|
20
|
+
return 0.0
|
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from django.contrib import admin
|
|
4
|
+
from django.db.models import QuerySet
|
|
5
|
+
from django.contrib import messages
|
|
4
6
|
|
|
5
7
|
from .models import Collection, CollectionGroup
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
@admin.register(Collection)
|
|
9
11
|
class CollectionAdmin(admin.ModelAdmin):
|
|
10
|
-
list_display = ['id', 'name', 'parent', 'is_deleted']
|
|
12
|
+
list_display = ['id', 'name', 'parent', 'is_deleted', 'tag_count']
|
|
11
13
|
list_select_related = ['parent']
|
|
12
14
|
list_filter = ['is_deleted', 'is_active']
|
|
13
15
|
search_fields = ['id', 'name', 'description', 'parent__name']
|
|
14
16
|
ordering = ['name']
|
|
15
17
|
autocomplete_fields = ['parent']
|
|
18
|
+
actions = ['set_tags_for_collections']
|
|
19
|
+
|
|
20
|
+
@admin.action(description="Set Tags for Collections (Allow 5 Seconds Per)")
|
|
21
|
+
def set_tags_for_collections(self, request, queryset: QuerySet[Collection]):
|
|
22
|
+
processed = 0
|
|
23
|
+
for collection in queryset:
|
|
24
|
+
collection.services.tag.process_and_set_tags()
|
|
25
|
+
processed += 1
|
|
26
|
+
|
|
27
|
+
messages.success(request, f'Successfully processed {processed} collections.')
|
|
28
|
+
|
|
29
|
+
def tag_count(self, collection: Collection):
|
|
30
|
+
return collection.tags.count()
|
|
31
|
+
|
|
32
|
+
tag_count.short_description = 'Tags'
|
|
16
33
|
|
|
17
34
|
|
|
18
35
|
@admin.register(CollectionGroup)
|
|
@@ -20,3 +37,5 @@ class CollectionGroupAdmin(admin.ModelAdmin):
|
|
|
20
37
|
list_display = ['id', 'collection', 'auth_group']
|
|
21
38
|
list_select_related = ['collection', 'auth_group']
|
|
22
39
|
search_fields = ['id', 'collection__name', 'auth_group__name']
|
|
40
|
+
|
|
41
|
+
|
|
@@ -5,6 +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.tags.mixins import TagsModelMixin
|
|
8
9
|
from django_spire.contrib.utils import truncate_string
|
|
9
10
|
from django_spire.history.mixins import HistoryModelMixin
|
|
10
11
|
from django_spire.knowledge.collection.querysets import CollectionQuerySet
|
|
@@ -12,7 +13,11 @@ from django_spire.knowledge.collection.services.service import CollectionGroupSe
|
|
|
12
13
|
CollectionService
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
class Collection(
|
|
16
|
+
class Collection(
|
|
17
|
+
HistoryModelMixin,
|
|
18
|
+
OrderingModelMixin,
|
|
19
|
+
TagsModelMixin,
|
|
20
|
+
):
|
|
16
21
|
parent = models.ForeignKey(
|
|
17
22
|
'self',
|
|
18
23
|
on_delete=models.CASCADE,
|
|
@@ -10,6 +10,7 @@ from django_spire.knowledge.collection.services.ordering_service import \
|
|
|
10
10
|
CollectionOrderingService
|
|
11
11
|
from django_spire.knowledge.collection.services.processor_service import \
|
|
12
12
|
CollectionProcessorService
|
|
13
|
+
from django_spire.knowledge.collection.services.tag_service import CollectionTagService
|
|
13
14
|
from django_spire.knowledge.collection.services.transformation_service import \
|
|
14
15
|
CollectionTransformationService
|
|
15
16
|
|
|
@@ -22,6 +23,7 @@ class CollectionService(BaseDjangoModelService['Collection']):
|
|
|
22
23
|
|
|
23
24
|
ordering = CollectionOrderingService()
|
|
24
25
|
processor = CollectionProcessorService()
|
|
26
|
+
tag = CollectionTagService()
|
|
25
27
|
transformation = CollectionTransformationService()
|
|
26
28
|
|
|
27
29
|
def save_model_obj(self, **field_data) -> tuple[Collection, bool]:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from dandy import Prompt
|
|
6
|
+
|
|
7
|
+
from django_spire.contrib.service import BaseDjangoModelService
|
|
8
|
+
from django_spire.core.tags.intelligence.tag_set_bot import TagSetBot
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from django_spire.knowledge.collection.models import Collection
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CollectionTagService(BaseDjangoModelService['Collection']):
|
|
15
|
+
obj: Collection
|
|
16
|
+
|
|
17
|
+
def process_and_set_tags(self):
|
|
18
|
+
collection_prompt = Prompt()
|
|
19
|
+
|
|
20
|
+
collection_prompt.sub_heading(self.obj.name)
|
|
21
|
+
collection_prompt.text(self.obj.description)
|
|
22
|
+
|
|
23
|
+
tag_set = TagSetBot().process(
|
|
24
|
+
content=collection_prompt
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
self.obj.set_tags_from_tag_set(
|
|
28
|
+
tag_set=tag_set,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def get_aggregated_tag_set(self):
|
|
32
|
+
tag_set = self.obj.tag_set
|
|
33
|
+
|
|
34
|
+
for collection in self.obj.children.active():
|
|
35
|
+
tag_set.update(collection.services.tag.get_aggregated_tag_set())
|
|
36
|
+
|
|
37
|
+
for entry in self.obj.entries.active():
|
|
38
|
+
tag_set.update(entry.tag_set)
|
|
39
|
+
|
|
40
|
+
return tag_set
|
|
41
|
+
|
|
42
|
+
def get_aggregated_tag_set_simplified(self):
|
|
43
|
+
tag_set = self.obj.tag_set_simplified
|
|
44
|
+
|
|
45
|
+
for collection in self.obj.children.active():
|
|
46
|
+
tag_set.update(collection.services.tag.get_aggregated_tag_set())
|
|
47
|
+
|
|
48
|
+
for entry in self.obj.entries.active():
|
|
49
|
+
tag_set.update(entry.tag_set_simplified)
|
|
50
|
+
|
|
51
|
+
return tag_set
|
|
52
|
+
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from django.contrib import admin
|
|
1
|
+
from django.contrib import admin, messages
|
|
2
|
+
from django.db.models import QuerySet
|
|
2
3
|
from django.urls import reverse
|
|
3
4
|
from django.utils.html import format_html
|
|
4
5
|
from django.utils.http import urlencode
|
|
@@ -8,12 +9,13 @@ from django_spire.knowledge.entry.models import Entry
|
|
|
8
9
|
|
|
9
10
|
@admin.register(Entry)
|
|
10
11
|
class EntryAdmin(admin.ModelAdmin):
|
|
11
|
-
list_display = ['name', 'current_version_link', 'collection', 'is_deleted']
|
|
12
|
+
list_display = ['name', 'current_version_link', 'collection', 'is_deleted', 'tag_count']
|
|
12
13
|
list_select_related = ['collection', 'current_version']
|
|
13
14
|
list_filter = ['is_deleted', 'is_active']
|
|
14
15
|
search_fields = ['name', 'collection__name']
|
|
15
16
|
ordering = ['name']
|
|
16
17
|
autocomplete_fields = ['collection', 'current_version']
|
|
18
|
+
actions = ['set_tags_for_entries']
|
|
17
19
|
|
|
18
20
|
def current_version_link(self, entry: Entry) -> str:
|
|
19
21
|
url = (
|
|
@@ -26,3 +28,17 @@ class EntryAdmin(admin.ModelAdmin):
|
|
|
26
28
|
|
|
27
29
|
current_version_link.short_description = 'Current Version'
|
|
28
30
|
current_version_link.allow_tags = True
|
|
31
|
+
|
|
32
|
+
@admin.action(description="Set Tags for Entries (Allow 5 Seconds Per)")
|
|
33
|
+
def set_tags_for_entries(self, request, queryset: QuerySet[Entry]):
|
|
34
|
+
processed = 0
|
|
35
|
+
for entry in queryset:
|
|
36
|
+
entry.services.tag.process_and_set_tags()
|
|
37
|
+
processed += 1
|
|
38
|
+
|
|
39
|
+
messages.success(request, f'Successfully processed {processed} entries.')
|
|
40
|
+
|
|
41
|
+
def tag_count(self, entry: Entry):
|
|
42
|
+
return entry.tags.count()
|
|
43
|
+
|
|
44
|
+
tag_count.short_description = 'Tags'
|
|
@@ -3,6 +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.tags.mixins import TagsModelMixin
|
|
6
7
|
from django_spire.contrib.utils import truncate_string
|
|
7
8
|
from django_spire.history.mixins import HistoryModelMixin
|
|
8
9
|
from django_spire.knowledge.collection.models import Collection
|
|
@@ -11,7 +12,11 @@ from django_spire.knowledge.entry.services.service import EntryService
|
|
|
11
12
|
from django_spire.knowledge.entry.version.models import EntryVersion
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class Entry(
|
|
15
|
+
class Entry(
|
|
16
|
+
HistoryModelMixin,
|
|
17
|
+
OrderingModelMixin,
|
|
18
|
+
TagsModelMixin
|
|
19
|
+
):
|
|
15
20
|
collection = models.ForeignKey(
|
|
16
21
|
Collection,
|
|
17
22
|
on_delete=models.CASCADE,
|
|
@@ -10,6 +10,7 @@ from django_spire.knowledge.entry.services.automation_service import \
|
|
|
10
10
|
from django_spire.knowledge.entry.services.factory_service import EntryFactoryService
|
|
11
11
|
from django_spire.knowledge.entry.services.processor_service import \
|
|
12
12
|
EntryProcessorService
|
|
13
|
+
from django_spire.knowledge.entry.services.tag_service import EntryTagService
|
|
13
14
|
from django_spire.knowledge.entry.services.tool_service import EntryToolService
|
|
14
15
|
from django_spire.knowledge.entry.services.transformation_services import \
|
|
15
16
|
EntryTransformationService
|
|
@@ -26,6 +27,7 @@ class EntryService(BaseDjangoModelService['Entry']):
|
|
|
26
27
|
factory = EntryFactoryService()
|
|
27
28
|
ordering = OrderingService()
|
|
28
29
|
processor = EntryProcessorService()
|
|
30
|
+
tag = EntryTagService()
|
|
29
31
|
tool = EntryToolService()
|
|
30
32
|
transformation = EntryTransformationService()
|
|
31
33
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from dandy import Prompt
|
|
6
|
+
|
|
7
|
+
from django_spire.contrib.service import BaseDjangoModelService
|
|
8
|
+
from django_spire.core.tags.intelligence.tag_set_bot import TagSetBot
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from django_spire.knowledge.entry.models import Entry
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EntryTagService(BaseDjangoModelService['Entry']):
|
|
16
|
+
obj: Entry
|
|
17
|
+
|
|
18
|
+
def process_and_set_tags(self):
|
|
19
|
+
entry_prompt = Prompt()
|
|
20
|
+
|
|
21
|
+
entry_prompt.text(self.obj.name)
|
|
22
|
+
|
|
23
|
+
for version_block in self.obj.current_version.blocks.all():
|
|
24
|
+
entry_prompt.text(f'{version_block.render_to_text()}')
|
|
25
|
+
|
|
26
|
+
tag_set = TagSetBot().process(
|
|
27
|
+
content=entry_prompt
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
self.obj.set_tags_from_tag_set(
|
|
31
|
+
tag_set=tag_set,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
@@ -9,8 +9,12 @@ app_name = 'json'
|
|
|
9
9
|
urlpatterns = [
|
|
10
10
|
path(
|
|
11
11
|
'<int:pk>/update_blocks/',
|
|
12
|
-
|
|
13
12
|
json_views.update_blocks_view,
|
|
14
13
|
name='update_blocks',
|
|
15
14
|
),
|
|
15
|
+
path(
|
|
16
|
+
'<int:pk>/update_entry_from_version/',
|
|
17
|
+
json_views.update_entry_from_version_view,
|
|
18
|
+
name='update_entry_from_version',
|
|
19
|
+
),
|
|
16
20
|
]
|
|
@@ -21,3 +21,12 @@ def update_blocks_view(request: WSGIRequest, pk: int) -> JsonResponse:
|
|
|
21
21
|
entry_version.services.processor.add_update_delete_blocks(block_data_list)
|
|
22
22
|
|
|
23
23
|
return JsonResponse({'type': 'success'})
|
|
24
|
+
|
|
25
|
+
@valid_ajax_request_required
|
|
26
|
+
@AppAuthController('knowledge').permission_required('can_change')
|
|
27
|
+
def update_entry_from_version_view(request: WSGIRequest, pk: int) -> JsonResponse:
|
|
28
|
+
entry_version = get_object_or_404(EntryVersion, pk=pk)
|
|
29
|
+
|
|
30
|
+
entry_version.entry.services.tag.process_and_set_tags()
|
|
31
|
+
|
|
32
|
+
return JsonResponse({'type': 'success'})
|
|
@@ -7,13 +7,13 @@ from django_spire.knowledge.collection.models import Collection
|
|
|
7
7
|
|
|
8
8
|
def get_collection_decoder() -> Decoder:
|
|
9
9
|
class CollectionDecoder(Decoder):
|
|
10
|
-
mapping_keys_description = 'Knowledge
|
|
10
|
+
mapping_keys_description = 'Knowledge Collections Tags'
|
|
11
11
|
mapping = {
|
|
12
12
|
**{
|
|
13
|
-
f'{collection.
|
|
13
|
+
f'{collection.services.tag.get_aggregated_tag_set_simplified()}': collection
|
|
14
14
|
for collection in Collection.objects.all().annotate_entry_count()
|
|
15
15
|
},
|
|
16
|
-
'No Matching Knowledge Collection
|
|
16
|
+
'No Matching Knowledge Collection Tags': None,
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
return CollectionDecoder()
|
|
19
|
+
return CollectionDecoder()
|
|
@@ -7,10 +7,10 @@ from django_spire.knowledge.collection.models import Collection
|
|
|
7
7
|
|
|
8
8
|
def get_entry_decoder(collection: Collection) -> Decoder:
|
|
9
9
|
class EntryDecoder(Decoder):
|
|
10
|
-
mapping_keys_description = 'Knowledge Entries'
|
|
10
|
+
mapping_keys_description = 'Knowledge Entries Tags'
|
|
11
11
|
mapping = {
|
|
12
12
|
**{
|
|
13
|
-
entry.
|
|
13
|
+
f'{entry.tag_set}': entry
|
|
14
14
|
for entry in collection.entries.all()
|
|
15
15
|
},
|
|
16
16
|
'No Matching Knowledge Entries': None
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from django.core.handlers.wsgi import WSGIRequest
|
|
6
|
+
|
|
7
|
+
from django_spire.ai.chat.message_intel import DefaultMessageIntel, BaseMessageIntel
|
|
6
8
|
from django_spire.knowledge.intelligence.bots.entry_search_llm_bot import EntrySearchBot
|
|
7
9
|
from django_spire.knowledge.intelligence.intel.entry_intel import EntriesIntel
|
|
8
10
|
from django_spire.knowledge.intelligence.intel.message_intel import KnowledgeMessageIntel
|
|
@@ -13,17 +15,21 @@ if TYPE_CHECKING:
|
|
|
13
15
|
from django.core.handlers.wsgi import WSGIRequest
|
|
14
16
|
from dandy.llm.request.message import MessageHistory
|
|
15
17
|
|
|
18
|
+
NO_KNOWLEDGE_MESSAGE_INTEL = DefaultMessageIntel(
|
|
19
|
+
text='Sorry, I could not find any information on that.'
|
|
20
|
+
)
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
def knowledge_search_workflow(
|
|
18
24
|
request: WSGIRequest,
|
|
19
25
|
user_input: str,
|
|
20
26
|
message_history: MessageHistory,
|
|
21
|
-
) ->
|
|
27
|
+
) -> BaseMessageIntel | None:
|
|
22
28
|
collection_decoder = get_collection_decoder()
|
|
23
29
|
collections = collection_decoder.process(user_input).values
|
|
24
30
|
|
|
25
31
|
if collections[0] is None:
|
|
26
|
-
return
|
|
32
|
+
return NO_KNOWLEDGE_MESSAGE_INTEL
|
|
27
33
|
|
|
28
34
|
entries = []
|
|
29
35
|
|
|
@@ -36,7 +42,7 @@ def knowledge_search_workflow(
|
|
|
36
42
|
entries = [entry for entry in entries if entry is not None]
|
|
37
43
|
|
|
38
44
|
if not entries:
|
|
39
|
-
return
|
|
45
|
+
return NO_KNOWLEDGE_MESSAGE_INTEL
|
|
40
46
|
|
|
41
47
|
entries_intel = EntriesIntel(entry_intel_list=[])
|
|
42
48
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-11-12 16:48
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('django_spire_core', '0001_initial'),
|
|
10
|
+
('django_spire_knowledge', '0007_alter_collection_options'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name='collection',
|
|
16
|
+
name='tags',
|
|
17
|
+
field=models.ManyToManyField(blank=True, editable=False, null=True, related_name='+', to='django_spire_core.tag'),
|
|
18
|
+
),
|
|
19
|
+
migrations.AddField(
|
|
20
|
+
model_name='entry',
|
|
21
|
+
name='tags',
|
|
22
|
+
field=models.ManyToManyField(blank=True, editable=False, null=True, related_name='+', to='django_spire_core.tag'),
|
|
23
|
+
),
|
|
24
|
+
]
|
|
@@ -46,4 +46,20 @@
|
|
|
46
46
|
Last Edits ETC
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
49
|
+
<div class="row">
|
|
50
|
+
<div class="col-12 col-lg-8 mx-auto pb-4">
|
|
51
|
+
Tags:
|
|
52
|
+
{% for tag in collection.tags.all %}
|
|
53
|
+
<span class="badge bg-secondary">{{ tag.name }}</span>
|
|
54
|
+
{% endfor %}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="row">
|
|
58
|
+
<div class="col-12 col-lg-8 mx-auto pb-4">
|
|
59
|
+
Aggregated Tags:
|
|
60
|
+
{% for tag in collection.services.tag.get_aggregated_tag_set %}
|
|
61
|
+
<span class="badge bg-secondary">{{ tag }}</span>
|
|
62
|
+
{% endfor %}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
49
65
|
{% endblock %}
|
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
KNOWLEDGE_ENTRY_VERSION_EDITOR?.readOnly?.toggle()
|
|
22
22
|
this.editor_is_readonly = KNOWLEDGE_ENTRY_VERSION_EDITOR?.readOnly?.isEnabled
|
|
23
23
|
|
|
24
|
+
if (this.editor_is_readonly) {
|
|
25
|
+
django_glue_fetch(
|
|
26
|
+
'{% url "django_spire:knowledge:entry:version:json:update_entry_from_version" pk=entry.id %}',
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
let view_mode = this.editor_is_readonly ? 'readonly' : 'edit'
|
|
25
31
|
|
|
26
32
|
let url = new URL(window.location)
|
|
@@ -16,32 +16,47 @@
|
|
|
16
16
|
{% endblock %}
|
|
17
17
|
|
|
18
18
|
{% block full_page_info_navigation %}
|
|
19
|
-
<div>
|
|
20
|
-
<div class="mb-3">
|
|
19
|
+
<div class="row">
|
|
20
|
+
<div class="col-12 mb-3">
|
|
21
21
|
{% include 'django_spire/element/attribute_element.html' with attribute_title='Author' attribute_value=entry.current_version.author.get_full_name %}
|
|
22
22
|
</div>
|
|
23
|
+
</div>
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
<div class="row">
|
|
26
|
+
<div class="col-12 mb-3">
|
|
25
27
|
{% include 'django_spire/element/attribute_element.html' with attribute_title='Status' %}
|
|
26
28
|
{% include 'django_spire/knowledge/entry/badge/entry_status_badge.html' with status=entry.current_version.status %}
|
|
27
29
|
</div>
|
|
30
|
+
</div>
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
{% if entry.current_version.published_datetime %}
|
|
33
|
+
<div class="row">
|
|
34
|
+
<div class="col-12 mb-3">
|
|
31
35
|
{% include 'django_spire/element/attribute_element.html' with attribute_title='Published' attribute_value=entry.current_version.published_datetime %}
|
|
32
36
|
</div>
|
|
33
|
-
|
|
37
|
+
</div>
|
|
38
|
+
{% endif %}
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
<div class="row">
|
|
41
|
+
<div class="col-12 mb-3">
|
|
36
42
|
{% include 'django_spire/element/attribute_element.html' with attribute_title='Last Edit' attribute_value=entry.current_version.last_edit_datetime %}
|
|
37
43
|
</div>
|
|
38
44
|
</div>
|
|
45
|
+
|
|
46
|
+
<div class="row">
|
|
47
|
+
<div class="col-12 mb-3">
|
|
48
|
+
{% include 'django_spire/element/attribute_element.html' with attribute_title='Tags' %}
|
|
49
|
+
{% for tag in entry.tags.all %}
|
|
50
|
+
<span class="badge bg-secondary">{{ tag.name }}</span>
|
|
51
|
+
{% endfor %}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
39
54
|
{% endblock %}
|
|
40
55
|
|
|
41
56
|
{% block knowledge_full_page_content %}
|
|
42
57
|
<div class="row justify-content-center ps-3">
|
|
43
58
|
<div class="col-12">
|
|
44
|
-
{% include 'django_spire/knowledge/entry/version/container/
|
|
59
|
+
{% include 'django_spire/knowledge/entry/version/container/editor_container.html' %}
|
|
45
60
|
</div>
|
|
46
61
|
</div>
|
|
47
62
|
{% endblock %}
|
|
@@ -49,6 +64,8 @@
|
|
|
49
64
|
{% block base_body_additional_bottom_js %}
|
|
50
65
|
{{ block.super }}
|
|
51
66
|
|
|
52
|
-
<script src="{% static 'django_spire/knowledge/entry/version/js/null_paragraph.js' %}?v={{ DJANGO_SPIRE_VERSION }}"
|
|
53
|
-
|
|
67
|
+
<script src="{% static 'django_spire/knowledge/entry/version/js/null_paragraph.js' %}?v={{ DJANGO_SPIRE_VERSION }}"
|
|
68
|
+
type="application/javascript"></script>
|
|
69
|
+
<script src="{% static 'django_spire/knowledge/entry/version/js/editor.js' %}?v={{ DJANGO_SPIRE_VERSION }}"
|
|
70
|
+
type="application/javascript"></script>
|
|
54
71
|
{% endblock %}
|
django_spire/settings.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-spire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.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.
|
|
@@ -49,7 +49,7 @@ Requires-Dist: beautifulsoup4>=4.14.2
|
|
|
49
49
|
Requires-Dist: boto3>=1.34.0
|
|
50
50
|
Requires-Dist: botocore>=1.34.0
|
|
51
51
|
Requires-Dist: crispy-bootstrap5==2024.10
|
|
52
|
-
Requires-Dist: dandy>=1.3.
|
|
52
|
+
Requires-Dist: dandy>=1.3.3
|
|
53
53
|
Requires-Dist: django>=5.1.8
|
|
54
54
|
Requires-Dist: django-crispy-forms==2.3
|
|
55
55
|
Requires-Dist: django-glue>=0.8.1
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
django_spire/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
django_spire/conf.py,sha256=c5Hs-7lk9T15254tOasiQ2ZTFLQIVJof9_QJDfm1PAI,933
|
|
3
|
-
django_spire/consts.py,sha256=
|
|
3
|
+
django_spire/consts.py,sha256=0pi9-zQb-ooeNNpnBBaPK6lSS7BO1hC2KSZHRlgbhcE,171
|
|
4
4
|
django_spire/exceptions.py,sha256=L5ndRO5ftMmh0pHkO2z_NG3LSGZviJ-dDHNT73SzTNw,48
|
|
5
|
-
django_spire/settings.py,sha256=
|
|
5
|
+
django_spire/settings.py,sha256=5gzQCMKAHcBYFREPrYLkQcqFj5gSSgyduUU1H2ztWN8,685
|
|
6
6
|
django_spire/urls.py,sha256=mKeZszb5U4iIGqddMb5Tt5fRC72U2wABEOi6mvOfEBU,656
|
|
7
7
|
django_spire/utils.py,sha256=kW0HP1xWj8Oz0h1GWs4NflnD8Jq8_F4hABwKTiT-Iyk,1006
|
|
8
8
|
django_spire/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -25,9 +25,9 @@ django_spire/ai/chat/auth/controller.py,sha256=l4xHQxSlPgS_QPvofsARUOMcXzVo3cTMs
|
|
|
25
25
|
django_spire/ai/chat/intelligence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
django_spire/ai/chat/intelligence/prompts.py,sha256=jnFaN7EUUQM-wxloWJZ2UAGw1RDXIOKkX4JfvT6ukxw,686
|
|
27
27
|
django_spire/ai/chat/intelligence/decoders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
django_spire/ai/chat/intelligence/decoders/tools.py,sha256=
|
|
28
|
+
django_spire/ai/chat/intelligence/decoders/tools.py,sha256=pC3Rvg0a1bgi0ESAsDH2HDJQTsu9aCcTslXfp4R7HwQ,1233
|
|
29
29
|
django_spire/ai/chat/intelligence/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
-
django_spire/ai/chat/intelligence/workflows/chat_workflow.py,sha256=
|
|
30
|
+
django_spire/ai/chat/intelligence/workflows/chat_workflow.py,sha256=VaaIqN2U5C1opyQXyJevDs9fDtfwn7gVm5-EZDju1EI,2475
|
|
31
31
|
django_spire/ai/chat/migrations/0001_initial.py,sha256=1cbREhX3_fNsbfumJoKAZ8w91Kq5NeXUn_iI45B7oGE,2632
|
|
32
32
|
django_spire/ai/chat/migrations/0002_remove_chatmessage_content_chatmessage__content_and_more.py,sha256=KeNT7VRFmwA74odh9wxIE1Cr4KAO4Tmtqu0FuI2AmK0,752
|
|
33
33
|
django_spire/ai/chat/migrations/0003_rename__content_chatmessage__intel_data_and_more.py,sha256=wAcJ6Ia3fWrGbqnVrqD2C3-3ijAot0LK-B3KZavoY_A,754
|
|
@@ -494,6 +494,8 @@ django_spire/core/management/commands/spire_startapp_pkg/template/templates/page
|
|
|
494
494
|
django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${list_page_template_name}.html.template,sha256=Ve0RVvWjMU6m33wXdDv10MkDoEFKWULuu-1_BK-O4aI,254
|
|
495
495
|
django_spire/core/middleware/__init__.py,sha256=BvBwVHLVebknJkObtKk_s5OqaIJaAmnavTvnnv4TBO8,149
|
|
496
496
|
django_spire/core/middleware/maintenance.py,sha256=jJtmz69UXJBBgPkq5SNspWuER1bGfAjSnsVipYS4TF0,921
|
|
497
|
+
django_spire/core/migrations/0001_initial.py,sha256=kbwHzfNnEf10O1rvqjXPhTyMT0ThZll_YtxHxDpq9GY,675
|
|
498
|
+
django_spire/core/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
497
499
|
django_spire/core/redirect/__init__.py,sha256=ubAsz_h3ufwgNNkdtqiJQDgXc6DodE2LTUyBwi9dgkE,71
|
|
498
500
|
django_spire/core/redirect/generic_redirect.py,sha256=bfpA2GSRbkf5Y_zqTiTGzakQauLumm3JbaYMzmsDhjA,1245
|
|
499
501
|
django_spire/core/redirect/safe_redirect.py,sha256=deGLqiR1wWwqlJ8BYp76qDUDHnfRrxL-1Vns3nozSG0,2901
|
|
@@ -572,6 +574,16 @@ django_spire/core/static/django_spire/js/modal.js,sha256=ay0Sovi0HhedEz3dRhB_INh
|
|
|
572
574
|
django_spire/core/static/django_spire/js/session_controller.js,sha256=aMom087y00MUhdxrZlrg89f88mBC7cWEhGWopeb5hlQ,232
|
|
573
575
|
django_spire/core/static/django_spire/js/theme.js,sha256=APZnTjuhgXV7m1vVlmvNIMZACJ0gG8uGeiyWSmhZTmQ,8512
|
|
574
576
|
django_spire/core/static/django_spire/js/ui.js,sha256=Xrnw6cMmddCiUKC7iAJn1U7lk3nMJYxFJJO-J5zCqSw,673
|
|
577
|
+
django_spire/core/tags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
578
|
+
django_spire/core/tags/mixins.py,sha256=ibnPcLmdB0js8ha4M93CqRv5kOQ7fijKBCLoxM9F7C0,1979
|
|
579
|
+
django_spire/core/tags/models.py,sha256=zKXITkpyYNe80-CmJh46qw0YqxbJM79ud2wpgznmZH4,819
|
|
580
|
+
django_spire/core/tags/querysets.py,sha256=vLdAV9KRTn-B9G9vhB4tQ3j-iNyz6OVZR5E9nVvAZY8,263
|
|
581
|
+
django_spire/core/tags/tools.py,sha256=CB8vOlFSmk8Q79x-5OGYXCGx-VteTgDAPUCVOYaTmhU,787
|
|
582
|
+
django_spire/core/tags/intelligence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
583
|
+
django_spire/core/tags/intelligence/tag_set_bot.py,sha256=-U-dkyodua48F6xatV1XHzeTTO-Zukl6rYOLMLxdIig,1289
|
|
584
|
+
django_spire/core/tags/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
585
|
+
django_spire/core/tags/tests/test_intelligence.py,sha256=UJj9OL_RaZQZ0tVih1VjntB9K5UkTg__VwE7iOr3Kog,1074
|
|
586
|
+
django_spire/core/tags/tests/test_tags.py,sha256=dTf0hkbgmnGK4HjdKO21dOdZLuZ2qqgeatsQvqqNeEg,3516
|
|
575
587
|
django_spire/core/templates/django_spire/403.html,sha256=35OX-3z-Yi6Igx9DTAcxNwWiZ17wXdm4wYGML4U2pns,37
|
|
576
588
|
django_spire/core/templates/django_spire/404.html,sha256=91sTr518M2YxQ8X3GlzDsNP6IVo3MW07tbHdcLBL8Iw,235
|
|
577
589
|
django_spire/core/templates/django_spire/500.html,sha256=8N-aqCoafjq2mFcjLZv-BkAEqRh2bzJ2SqojlboS8g4,247
|
|
@@ -796,9 +808,9 @@ django_spire/knowledge/models.py,sha256=9NtBiv6HD6dcfL9AGMf5n6Z14hKciDkQ7Fu2zzOB
|
|
|
796
808
|
django_spire/knowledge/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
797
809
|
django_spire/knowledge/auth/controller.py,sha256=ktTJ7gjtbYmCYESZnK97q7_fPojTAScZXRysg2F_86Y,929
|
|
798
810
|
django_spire/knowledge/collection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
799
|
-
django_spire/knowledge/collection/admin.py,sha256=
|
|
811
|
+
django_spire/knowledge/collection/admin.py,sha256=9OKEgiSDiSHX6ZwB11l6mCx-jdF4VKfOe-SwFBdjKK4,1370
|
|
800
812
|
django_spire/knowledge/collection/forms.py,sha256=7JovVxNzE67hSydDa-1JGK4U4zPyzOj605QyEl0JkP8,238
|
|
801
|
-
django_spire/knowledge/collection/models.py,sha256=
|
|
813
|
+
django_spire/knowledge/collection/models.py,sha256=_wqLIzOHwvsVxdc-GjEZeXWKo_MOHF7DyaHCEPuaCwM,2390
|
|
802
814
|
django_spire/knowledge/collection/querysets.py,sha256=8DWTGvo4WovXQ9n7XRLoxtyDdBUka2N-kcPUnJVOhOo,2479
|
|
803
815
|
django_spire/knowledge/collection/seeding/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
804
816
|
django_spire/knowledge/collection/seeding/seed.py,sha256=i4VEvScJvZ1SUV9ukKZp0qdh_owyLyVSHGgl8vIzhwQ,303
|
|
@@ -807,7 +819,8 @@ django_spire/knowledge/collection/services/__init__.py,sha256=47DEQpj8HBSa-_TImW
|
|
|
807
819
|
django_spire/knowledge/collection/services/factory_service.py,sha256=92DLZODSRPF-iA9r3Jgys2GzbC39KssGEk9eOJ5wZjs,1298
|
|
808
820
|
django_spire/knowledge/collection/services/ordering_service.py,sha256=vTkSdt1lVT4VxSK3GhZibj8FPDDy5butwPkVzqmm07g,1243
|
|
809
821
|
django_spire/knowledge/collection/services/processor_service.py,sha256=3ArKBndoL3HEvH_1EBqfiw7AnbFYZ0WmBgcPV8BHdLQ,911
|
|
810
|
-
django_spire/knowledge/collection/services/service.py,sha256=
|
|
822
|
+
django_spire/knowledge/collection/services/service.py,sha256=sEamdqRcKdNcL6yz18hok0jww3Z4VRCPWrMF2QLvKGE,1830
|
|
823
|
+
django_spire/knowledge/collection/services/tag_service.py,sha256=7EUIoTn70oAvcsX53RvKMLTZ_sySmBNgWhBvg8RCesE,1430
|
|
811
824
|
django_spire/knowledge/collection/services/transformation_service.py,sha256=ME_jYURSG7at4oujTbcRzIVXyKnb75Xi6M4Nclbw6Yo,3464
|
|
812
825
|
django_spire/knowledge/collection/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
813
826
|
django_spire/knowledge/collection/tests/factories.py,sha256=NUu4OliEZIQL9c05RPIPdzYP3Dtn1szLZxP4Wl-kX2w,429
|
|
@@ -822,13 +835,13 @@ django_spire/knowledge/collection/urls/form_urls.py,sha256=yHU3sbaA3w-m9rVvon6nX
|
|
|
822
835
|
django_spire/knowledge/collection/urls/json_urls.py,sha256=XZp5wt8m9x38Uuy1dhegqZ4qlJLZK2EgJsLltx97o0Q,195
|
|
823
836
|
django_spire/knowledge/collection/urls/page_urls.py,sha256=M8zZJJzVmMx7QUc3uGIrjyb8-Kz00fz7u0mHYqFZf3M,280
|
|
824
837
|
django_spire/knowledge/collection/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
825
|
-
django_spire/knowledge/collection/views/form_views.py,sha256=
|
|
838
|
+
django_spire/knowledge/collection/views/form_views.py,sha256=ziQhlrLEJ62VzYiIAApd0ZThM2jc-who-qw0_BlO-VI,3014
|
|
826
839
|
django_spire/knowledge/collection/views/json_views.py,sha256=uzSD83O7p-L5Oh8jo_Ejh-4lbo5zXjLkZGe0cn6SXSw,1030
|
|
827
840
|
django_spire/knowledge/collection/views/page_views.py,sha256=jiKsIqi9pvSY5KbLLGDdTHSPHXmnd-7oyYXckF2ONh4,2045
|
|
828
841
|
django_spire/knowledge/entry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
829
|
-
django_spire/knowledge/entry/admin.py,sha256=
|
|
842
|
+
django_spire/knowledge/entry/admin.py,sha256=pY-0sYx24Su0y7sb1I2-GMCZxFEZj9oXhRckEdHNKno,1568
|
|
830
843
|
django_spire/knowledge/entry/forms.py,sha256=Mhol37dMJP-pnex5IPxQVdchYcFBXnGzcWoY0SkU7lo,339
|
|
831
|
-
django_spire/knowledge/entry/models.py,sha256=
|
|
844
|
+
django_spire/knowledge/entry/models.py,sha256=RSWXFLWAPZ43klgZvqXEzYl39g4hNIUPmX7btOrzbGk,2141
|
|
832
845
|
django_spire/knowledge/entry/querysets.py,sha256=oVTylFT_Zx0elT5IVbYZRa0FwyGd9YaRz0vdcumfrsU,1121
|
|
833
846
|
django_spire/knowledge/entry/seeding/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
834
847
|
django_spire/knowledge/entry/seeding/seed.py,sha256=n7ereq118GuqV1luNih7nuVpkrZpkBgPWt0FuE4Bl88,105
|
|
@@ -837,7 +850,8 @@ django_spire/knowledge/entry/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
837
850
|
django_spire/knowledge/entry/services/automation_service.py,sha256=0_Ypk5cXhGvkQLkmKBArFTrE_sZhXCnBencmuJeLxMI,1826
|
|
838
851
|
django_spire/knowledge/entry/services/factory_service.py,sha256=bgUEHjdc9u2mxUJFy26qYOxUiJRHmhtcoH6AG3xzU0I,1379
|
|
839
852
|
django_spire/knowledge/entry/services/processor_service.py,sha256=sfAM5ku7Td4k0jCe2szyoR72Bt1_OS75UKh7YPL4l2s,494
|
|
840
|
-
django_spire/knowledge/entry/services/service.py,sha256
|
|
853
|
+
django_spire/knowledge/entry/services/service.py,sha256=-A8_UcmQ8aLvkRNzGzTMA4faN9UWOd5pdlFlujB1YHI,1912
|
|
854
|
+
django_spire/knowledge/entry/services/tag_service.py,sha256=AA4xDmofhuFOxa6HuKqlM9_SY-kjPWLKAnPUhvA7Is4,793
|
|
841
855
|
django_spire/knowledge/entry/services/tool_service.py,sha256=QL6w8t284LfSOJrQaF46uWInd4t413WYxcXCWsat_jE,1083
|
|
842
856
|
django_spire/knowledge/entry/services/transformation_services.py,sha256=6WPQxW-_XATi8HCwshtJjoYbQFJcIdCqFaADHnTyk1U,2687
|
|
843
857
|
django_spire/knowledge/entry/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -903,11 +917,11 @@ django_spire/knowledge/entry/version/tests/test_urls/__init__.py,sha256=47DEQpj8
|
|
|
903
917
|
django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py,sha256=WFN-aR6NmZGNKv03Jlw10YR730JYpbydYHDQPPbbkJI,1161
|
|
904
918
|
django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py,sha256=l3k7lXeRNbbtIXZtt8LLDacDozYjTzinSCAjJdPeGC4,946
|
|
905
919
|
django_spire/knowledge/entry/version/urls/__init__.py,sha256=e0MeLIxK5W4H68nK3A5TKbD7ZJFwFFJ2bZNhh2T-4uk,394
|
|
906
|
-
django_spire/knowledge/entry/version/urls/json_urls.py,sha256=
|
|
920
|
+
django_spire/knowledge/entry/version/urls/json_urls.py,sha256=w1QWnVGHHIrLgXJYoDBJB0q07WCZzQTBeBotLtXQjoE,414
|
|
907
921
|
django_spire/knowledge/entry/version/urls/page_urls.py,sha256=z8pfK548b4xr6UIE3pnPNTUjSeZaeBac2MQeL4VyJTo,204
|
|
908
922
|
django_spire/knowledge/entry/version/urls/redirect_urls.py,sha256=9X7UqKYIxwDRVPHTwT1zywpWybD1hJ923p3cX1itZ5Q,219
|
|
909
923
|
django_spire/knowledge/entry/version/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
910
|
-
django_spire/knowledge/entry/version/views/json_views.py,sha256=
|
|
924
|
+
django_spire/knowledge/entry/version/views/json_views.py,sha256=gauZ4YvUks7GNWR212aaPKgE6ym5YDKVLChKWGfqljs,1180
|
|
911
925
|
django_spire/knowledge/entry/version/views/page_views.py,sha256=rRoaqlChmcnWVLO02smw_zIHrec-3MtQ-FcDGzOQZh0,1698
|
|
912
926
|
django_spire/knowledge/entry/version/views/redirect_views.py,sha256=m6RPYfBcnOq-iU2QND56IfCHU7wCqF9aOiQUCbqP_Aw,754
|
|
913
927
|
django_spire/knowledge/entry/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -919,14 +933,14 @@ django_spire/knowledge/intelligence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQ
|
|
|
919
933
|
django_spire/knowledge/intelligence/bots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
920
934
|
django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py,sha256=02fRELEyRjqRc1o_McQPWucQOTQtpIO6_v8-3OO7mLs,1689
|
|
921
935
|
django_spire/knowledge/intelligence/decoders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
922
|
-
django_spire/knowledge/intelligence/decoders/collection_decoder.py,sha256=
|
|
923
|
-
django_spire/knowledge/intelligence/decoders/entry_decoder.py,sha256=
|
|
936
|
+
django_spire/knowledge/intelligence/decoders/collection_decoder.py,sha256=Vqh-JQukPatbxvV3NMWKvl8Np3bgRtDxQZjd2T3E0Rc,599
|
|
937
|
+
django_spire/knowledge/intelligence/decoders/entry_decoder.py,sha256=q6RzbCz0Ph00wJ9p8Ujp-2hYwppeRZa8Pj_-0YDwYlI,516
|
|
924
938
|
django_spire/knowledge/intelligence/intel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
925
939
|
django_spire/knowledge/intelligence/intel/collection_intel.py,sha256=4ci6_bVUimj_33FZgQ7wCpESXrKBMxHGF0yyfrstmvI,144
|
|
926
940
|
django_spire/knowledge/intelligence/intel/entry_intel.py,sha256=lGlGqeruV5oOu0aArGA_Bxa1pqAI7lr1MD7UATAy2Sg,445
|
|
927
941
|
django_spire/knowledge/intelligence/intel/message_intel.py,sha256=yLTlFZ3dRJU800yOtp3Tme3TZdmi8pf02gbo6uZlUps,420
|
|
928
942
|
django_spire/knowledge/intelligence/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
929
|
-
django_spire/knowledge/intelligence/workflows/knowledge_workflow.py,sha256=
|
|
943
|
+
django_spire/knowledge/intelligence/workflows/knowledge_workflow.py,sha256=s_KXlpYSwG89slEItbEA9pEPiH8DyAmRQ-VpZEP36Ns,1894
|
|
930
944
|
django_spire/knowledge/migrations/0001_initial.py,sha256=0oAx4e0Tu1wbcMwQZ4I_B2X_EGdEA9Qz_UBdVNGc3yE,5487
|
|
931
945
|
django_spire/knowledge/migrations/0002_alter_entryversionblock_type.py,sha256=Eg8tLjDO0nJ3go_jgVvoqwYKFOpYUUyNtZtLI2V3IQQ,527
|
|
932
946
|
django_spire/knowledge/migrations/0003_alter_collection_order_alter_entry_order_and_more.py,sha256=nIIUg1lHQ1iLbPlOulwZgzHrP1erGMYVXDaEdDXv4ik,749
|
|
@@ -934,6 +948,7 @@ django_spire/knowledge/migrations/0004_alter_collection_options_collectiongroup.
|
|
|
934
948
|
django_spire/knowledge/migrations/0005_entryversionblock__tunes_data_and_more.py,sha256=sA_YCnFX6R2Wt9OecVpW4Sj27k2iRcw4KbyJVdcQYqY,771
|
|
935
949
|
django_spire/knowledge/migrations/0006_alter_entryversionblock_type.py,sha256=-FPS118i3wwbfuXIPv4fNQiKu59X35E2r2zEFgDyNgo,516
|
|
936
950
|
django_spire/knowledge/migrations/0007_alter_collection_options.py,sha256=jUqOHnY9T7QjguVhNC2uYVJzlWqOwWeSVe26ElySkY0,587
|
|
951
|
+
django_spire/knowledge/migrations/0008_collection_tags_entry_tags.py,sha256=QnCm7ULhPfBUJ8Ut7CKv4sG41S4gRkZX06jcWKBvB_M,754
|
|
937
952
|
django_spire/knowledge/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
938
953
|
django_spire/knowledge/seeding/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
939
954
|
django_spire/knowledge/seeding/seed.py,sha256=LRhVfPHiFluSLLtjSY44YM9Oo1Hvfs_U-ksRvKk7pOg,97
|
|
@@ -950,7 +965,7 @@ django_spire/knowledge/templates/django_spire/knowledge/collection/container/det
|
|
|
950
965
|
django_spire/knowledge/templates/django_spire/knowledge/collection/element/ellipsis_dropdown.html,sha256=o9cceexfwUSDtac4uhlUm4LimnBQrgTP6QPkpJF6DD0,1376
|
|
951
966
|
django_spire/knowledge/templates/django_spire/knowledge/collection/form/form.html,sha256=gDjvFBpNQHGn4PNJBi4pBw89-IakpyrC23D9dr0xQ-c,1902
|
|
952
967
|
django_spire/knowledge/templates/django_spire/knowledge/collection/item/collection_item.html,sha256=gaOqh1wxf5IcWdxWWiYyz6HWjoPyn6FohaTMIssJ67k,517
|
|
953
|
-
django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html,sha256=
|
|
968
|
+
django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html,sha256=v6lLzrubEYzVgoTKt_Oh3xAV0agUkWmyTFtAmD5uQcg,2281
|
|
954
969
|
django_spire/knowledge/templates/django_spire/knowledge/collection/page/form_page.html,sha256=eD190iYnmx-6u8Y0EAExDdilPtxTlS_ic6-WBfTtj2s,274
|
|
955
970
|
django_spire/knowledge/templates/django_spire/knowledge/container/container.html,sha256=Fi-PL8neKrmGeLQtvhNiGoimpen86m4r9YlAS-amWXI,1220
|
|
956
971
|
django_spire/knowledge/templates/django_spire/knowledge/entry/badge/entry_status_badge.html,sha256=qz7Y2ac1T4qu0Nr_Yj4BdUbQamgA6Llq7TmoXAf5Arw,381
|
|
@@ -966,8 +981,8 @@ django_spire/knowledge/templates/django_spire/knowledge/entry/item/list_item.htm
|
|
|
966
981
|
django_spire/knowledge/templates/django_spire/knowledge/entry/modal/publish_confirm_modal.html,sha256=7SsTjvk_iOdBgnYy83pMBPIdbuvJadd4h5XI5mPos5o,724
|
|
967
982
|
django_spire/knowledge/templates/django_spire/knowledge/entry/page/form_page.html,sha256=EBfDjDBvo7IR6f5joTtZjzK5LtZb4rt0U1FO1HQNt88,291
|
|
968
983
|
django_spire/knowledge/templates/django_spire/knowledge/entry/page/import_form_page.html,sha256=TK8Bc7mUZwsmV1Xm9FPyWz7Hws68Ijy4c55orvNoOC4,308
|
|
969
|
-
django_spire/knowledge/templates/django_spire/knowledge/entry/version/container/
|
|
970
|
-
django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html,sha256=
|
|
984
|
+
django_spire/knowledge/templates/django_spire/knowledge/entry/version/container/editor_container.html,sha256=NAtXEq8UJOV9_vp3l5HIIdQp-lZhNVW4luF0Nvv8Sl8,3389
|
|
985
|
+
django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html,sha256=Q6bPXeIORGQMZ4Wb6YDsWWoMUKws6a-oGhl4mv7syUE,3019
|
|
971
986
|
django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/form_page.html,sha256=ufUISOfNmk45hl7F-ujtAzpHdEmUxh55odW0eyL6SMU,722
|
|
972
987
|
django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html,sha256=fpCJAwgxYqzJPLV63mLmUFjfEFk98t4FBX0RkSvDnzQ,698
|
|
973
988
|
django_spire/knowledge/templates/django_spire/knowledge/page/full_page.html,sha256=vz3WQ6SAL43QALRf0haaLfNkUfIQQemR0FAZQ2tznV4,1331
|
|
@@ -1127,8 +1142,8 @@ django_spire/theme/urls/page_urls.py,sha256=S8nkKkgbhG3XHI3uMUL-piOjXIrRkuY2UlM_
|
|
|
1127
1142
|
django_spire/theme/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1128
1143
|
django_spire/theme/views/json_views.py,sha256=W1khC2K_EMbEzAFmMxC_P76_MFnkRH4-eVdodrRfAhw,1904
|
|
1129
1144
|
django_spire/theme/views/page_views.py,sha256=pHr8iekjtR99xs7w1taj35HEo133Svq1dvDD0y0VL1c,3933
|
|
1130
|
-
django_spire-0.
|
|
1131
|
-
django_spire-0.
|
|
1132
|
-
django_spire-0.
|
|
1133
|
-
django_spire-0.
|
|
1134
|
-
django_spire-0.
|
|
1145
|
+
django_spire-0.20.1.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
|
|
1146
|
+
django_spire-0.20.1.dist-info/METADATA,sha256=2DlaKuP0TZp6LeY4ytPWI7ckgoQS_4lF5GonhxR-E6A,4967
|
|
1147
|
+
django_spire-0.20.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
1148
|
+
django_spire-0.20.1.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
|
|
1149
|
+
django_spire-0.20.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|