django-cfg 1.1.82__py3-none-any.whl → 1.2.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_cfg/__init__.py +20 -448
- django_cfg/apps/accounts/README.md +3 -3
- django_cfg/apps/accounts/admin/__init__.py +0 -2
- django_cfg/apps/accounts/admin/activity.py +2 -9
- django_cfg/apps/accounts/admin/filters.py +0 -42
- django_cfg/apps/accounts/admin/inlines.py +8 -8
- django_cfg/apps/accounts/admin/otp.py +5 -5
- django_cfg/apps/accounts/admin/registration_source.py +1 -8
- django_cfg/apps/accounts/admin/user.py +12 -20
- django_cfg/apps/accounts/managers/user_manager.py +2 -129
- django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
- django_cfg/apps/accounts/models.py +3 -123
- django_cfg/apps/accounts/serializers/otp.py +40 -44
- django_cfg/apps/accounts/serializers/profile.py +0 -2
- django_cfg/apps/accounts/services/otp_service.py +98 -186
- django_cfg/apps/accounts/signals.py +25 -15
- django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
- django_cfg/apps/accounts/views/otp.py +35 -36
- django_cfg/apps/agents/README.md +129 -0
- django_cfg/apps/agents/__init__.py +68 -0
- django_cfg/apps/agents/admin/__init__.py +17 -0
- django_cfg/apps/agents/admin/execution_admin.py +460 -0
- django_cfg/apps/agents/admin/registry_admin.py +360 -0
- django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
- django_cfg/apps/agents/apps.py +29 -0
- django_cfg/apps/agents/core/__init__.py +20 -0
- django_cfg/apps/agents/core/agent.py +281 -0
- django_cfg/apps/agents/core/dependencies.py +154 -0
- django_cfg/apps/agents/core/exceptions.py +66 -0
- django_cfg/apps/agents/core/models.py +106 -0
- django_cfg/apps/agents/core/orchestrator.py +391 -0
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/agents/integration/__init__.py +14 -0
- django_cfg/apps/agents/integration/middleware.py +80 -0
- django_cfg/apps/agents/integration/registry.py +345 -0
- django_cfg/apps/agents/integration/signals.py +50 -0
- django_cfg/apps/agents/management/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/create_agent.py +365 -0
- django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
- django_cfg/apps/agents/managers/__init__.py +23 -0
- django_cfg/apps/agents/managers/execution.py +236 -0
- django_cfg/apps/agents/managers/registry.py +254 -0
- django_cfg/apps/agents/managers/toolsets.py +496 -0
- django_cfg/apps/agents/migrations/0001_initial.py +286 -0
- django_cfg/apps/agents/migrations/__init__.py +5 -0
- django_cfg/apps/agents/models/__init__.py +15 -0
- django_cfg/apps/agents/models/execution.py +215 -0
- django_cfg/apps/agents/models/registry.py +220 -0
- django_cfg/apps/agents/models/toolsets.py +305 -0
- django_cfg/apps/agents/patterns/__init__.py +24 -0
- django_cfg/apps/agents/patterns/content_agents.py +234 -0
- django_cfg/apps/agents/toolsets/__init__.py +15 -0
- django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
- django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
- django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
- django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
- django_cfg/apps/agents/urls.py +46 -0
- django_cfg/apps/knowbase/README.md +150 -0
- django_cfg/apps/knowbase/__init__.py +27 -0
- django_cfg/apps/knowbase/admin/__init__.py +23 -0
- django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
- django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
- django_cfg/apps/knowbase/admin/document_admin.py +650 -0
- django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
- django_cfg/apps/knowbase/apps.py +81 -0
- django_cfg/apps/knowbase/config/README.md +176 -0
- django_cfg/apps/knowbase/config/__init__.py +51 -0
- django_cfg/apps/knowbase/config/constance_fields.py +186 -0
- django_cfg/apps/knowbase/config/constance_settings.py +200 -0
- django_cfg/apps/knowbase/config/settings.py +450 -0
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/management/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
- django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
- django_cfg/apps/knowbase/managers/__init__.py +22 -0
- django_cfg/apps/knowbase/managers/archive.py +426 -0
- django_cfg/apps/knowbase/managers/base.py +32 -0
- django_cfg/apps/knowbase/managers/chat.py +141 -0
- django_cfg/apps/knowbase/managers/document.py +203 -0
- django_cfg/apps/knowbase/managers/external_data.py +471 -0
- django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
- django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
- django_cfg/apps/knowbase/migrations/__init__.py +5 -0
- django_cfg/apps/knowbase/mixins/__init__.py +15 -0
- django_cfg/apps/knowbase/mixins/config.py +108 -0
- django_cfg/apps/knowbase/mixins/creator.py +81 -0
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
- django_cfg/apps/knowbase/mixins/service.py +362 -0
- django_cfg/apps/knowbase/models/__init__.py +41 -0
- django_cfg/apps/knowbase/models/archive.py +599 -0
- django_cfg/apps/knowbase/models/base.py +58 -0
- django_cfg/apps/knowbase/models/chat.py +157 -0
- django_cfg/apps/knowbase/models/document.py +267 -0
- django_cfg/apps/knowbase/models/external_data.py +376 -0
- django_cfg/apps/knowbase/serializers/__init__.py +68 -0
- django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
- django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
- django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
- django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
- django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
- django_cfg/apps/knowbase/services/__init__.py +40 -0
- django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
- django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
- django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
- django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
- django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
- django_cfg/apps/knowbase/services/base.py +53 -0
- django_cfg/apps/knowbase/services/chat_service.py +239 -0
- django_cfg/apps/knowbase/services/document_service.py +144 -0
- django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
- django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
- django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
- django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
- django_cfg/apps/knowbase/services/embedding/models.py +229 -0
- django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
- django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
- django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
- django_cfg/apps/knowbase/services/search_service.py +293 -0
- django_cfg/apps/knowbase/signals/__init__.py +21 -0
- django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
- django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
- django_cfg/apps/knowbase/signals/document_signals.py +143 -0
- django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
- django_cfg/apps/knowbase/tasks/__init__.py +39 -0
- django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
- django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
- django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
- django_cfg/apps/knowbase/urls.py +43 -0
- django_cfg/apps/knowbase/utils/__init__.py +12 -0
- django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
- django_cfg/apps/knowbase/utils/text_processing.py +375 -0
- django_cfg/apps/knowbase/utils/validation.py +99 -0
- django_cfg/apps/knowbase/views/__init__.py +28 -0
- django_cfg/apps/knowbase/views/archive_views.py +469 -0
- django_cfg/apps/knowbase/views/base.py +49 -0
- django_cfg/apps/knowbase/views/chat_views.py +181 -0
- django_cfg/apps/knowbase/views/document_views.py +183 -0
- django_cfg/apps/knowbase/views/public_views.py +129 -0
- django_cfg/apps/leads/admin.py +70 -0
- django_cfg/apps/newsletter/admin.py +234 -0
- django_cfg/apps/newsletter/admin_filters.py +124 -0
- django_cfg/apps/support/admin.py +196 -0
- django_cfg/apps/support/admin_filters.py +71 -0
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/apps/urls.py +5 -4
- django_cfg/cli/README.md +1 -1
- django_cfg/cli/commands/create_project.py +2 -2
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/config.py +44 -0
- django_cfg/core/config.py +29 -82
- django_cfg/core/environment.py +1 -1
- django_cfg/core/generation.py +19 -107
- django_cfg/{integration.py → core/integration.py} +18 -16
- django_cfg/core/validation.py +1 -1
- django_cfg/management/__init__.py +1 -1
- django_cfg/management/commands/__init__.py +1 -1
- django_cfg/management/commands/auto_generate.py +482 -0
- django_cfg/management/commands/migrator.py +19 -101
- django_cfg/management/commands/test_email.py +1 -1
- django_cfg/middleware/README.md +0 -158
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/api.py +145 -0
- django_cfg/models/base.py +287 -0
- django_cfg/models/cache.py +4 -4
- django_cfg/models/constance.py +25 -88
- django_cfg/models/database.py +9 -9
- django_cfg/models/drf.py +3 -36
- django_cfg/models/email.py +163 -0
- django_cfg/models/environment.py +276 -0
- django_cfg/models/limits.py +1 -1
- django_cfg/models/logging.py +366 -0
- django_cfg/models/revolution.py +41 -2
- django_cfg/models/security.py +125 -0
- django_cfg/models/services.py +1 -1
- django_cfg/modules/__init__.py +2 -56
- django_cfg/modules/base.py +78 -52
- django_cfg/modules/django_currency/service.py +2 -2
- django_cfg/modules/django_email.py +2 -2
- django_cfg/modules/django_health.py +267 -0
- django_cfg/modules/django_llm/llm/client.py +91 -19
- django_cfg/modules/django_llm/translator/translator.py +2 -2
- django_cfg/modules/django_logger.py +2 -2
- django_cfg/modules/django_ngrok.py +2 -2
- django_cfg/modules/django_tasks.py +68 -3
- django_cfg/modules/django_telegram.py +3 -3
- django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
- django_cfg/modules/django_twilio/service.py +2 -2
- django_cfg/modules/django_twilio/simple_service.py +2 -2
- django_cfg/modules/django_twilio/twilio_service.py +2 -2
- django_cfg/modules/django_unfold/__init__.py +69 -0
- django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
- django_cfg/modules/django_unfold/dashboard.py +278 -0
- django_cfg/modules/django_unfold/icons/README.md +145 -0
- django_cfg/modules/django_unfold/icons/__init__.py +12 -0
- django_cfg/modules/django_unfold/icons/constants.py +2851 -0
- django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
- django_cfg/modules/django_unfold/models/__init__.py +42 -0
- django_cfg/modules/django_unfold/models/config.py +601 -0
- django_cfg/modules/django_unfold/models/dashboard.py +206 -0
- django_cfg/modules/django_unfold/models/dropdown.py +40 -0
- django_cfg/modules/django_unfold/models/navigation.py +73 -0
- django_cfg/modules/django_unfold/models/tabs.py +25 -0
- django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
- django_cfg/modules/django_unfold/utils.py +140 -0
- django_cfg/registry/__init__.py +23 -0
- django_cfg/registry/core.py +61 -0
- django_cfg/registry/exceptions.py +11 -0
- django_cfg/registry/modules.py +12 -0
- django_cfg/registry/services.py +26 -0
- django_cfg/registry/third_party.py +52 -0
- django_cfg/routing/__init__.py +19 -0
- django_cfg/routing/callbacks.py +198 -0
- django_cfg/routing/routers.py +48 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
- django_cfg/templatetags/__init__.py +0 -0
- django_cfg/templatetags/django_cfg.py +33 -0
- django_cfg/urls.py +33 -0
- django_cfg/utils/path_resolution.py +1 -1
- django_cfg/utils/smart_defaults.py +7 -61
- django_cfg/utils/toolkit.py +663 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/METADATA +83 -86
- django_cfg-1.2.1.dist-info/RECORD +441 -0
- django_cfg/archive/django_sample.zip +0 -0
- django_cfg/models/unfold.py +0 -271
- django_cfg/modules/unfold/__init__.py +0 -29
- django_cfg/modules/unfold/dashboard.py +0 -318
- django_cfg/pyproject.toml +0 -370
- django_cfg/routers.py +0 -83
- django_cfg-1.1.82.dist-info/RECORD +0 -278
- /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
- /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
- /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
- /django_cfg/{version_check.py → utils/version_check.py} +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,386 @@
|
|
1
|
+
"""
|
2
|
+
Chat admin interfaces with Unfold optimization.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.contrib import admin
|
6
|
+
from django.utils.html import format_html
|
7
|
+
from django.urls import reverse
|
8
|
+
from django.db import models
|
9
|
+
from django.db.models import Count, Sum, Avg, Q
|
10
|
+
from unfold.admin import ModelAdmin, TabularInline
|
11
|
+
from unfold.decorators import display
|
12
|
+
from unfold.contrib.filters.admin import AutocompleteSelectFilter
|
13
|
+
|
14
|
+
from ..models import ChatSession, ChatMessage
|
15
|
+
|
16
|
+
|
17
|
+
class ChatMessageInline(TabularInline):
|
18
|
+
"""Inline for chat messages with Unfold styling."""
|
19
|
+
|
20
|
+
model = ChatMessage
|
21
|
+
verbose_name = "Chat Message"
|
22
|
+
verbose_name_plural = "💬 Chat Messages (Read-only)"
|
23
|
+
extra = 0
|
24
|
+
max_num = 0 # No new messages allowed
|
25
|
+
can_delete = False # Prevent deletion through inline
|
26
|
+
show_change_link = True # Allow viewing individual messages
|
27
|
+
|
28
|
+
def has_add_permission(self, request, obj=None):
|
29
|
+
"""Disable adding new messages through inline."""
|
30
|
+
return False
|
31
|
+
|
32
|
+
def has_change_permission(self, request, obj=None):
|
33
|
+
"""Disable editing messages through inline."""
|
34
|
+
return False
|
35
|
+
|
36
|
+
def has_delete_permission(self, request, obj=None):
|
37
|
+
"""Disable deleting messages through inline."""
|
38
|
+
return False
|
39
|
+
|
40
|
+
fields = [
|
41
|
+
'short_uuid', 'role_badge_inline', 'content_preview_inline', 'tokens_used',
|
42
|
+
'cost_display_inline', 'processing_time_inline', 'created_at'
|
43
|
+
]
|
44
|
+
readonly_fields = [
|
45
|
+
'short_uuid', 'role_badge_inline', 'content_preview_inline', 'tokens_used',
|
46
|
+
'cost_display_inline', 'processing_time_inline', 'created_at'
|
47
|
+
]
|
48
|
+
|
49
|
+
# Unfold specific options
|
50
|
+
hide_title = False # Show titles for better UX
|
51
|
+
classes = ['collapse'] # Collapsed by default
|
52
|
+
|
53
|
+
@display(description="Role")
|
54
|
+
def role_badge_inline(self, obj):
|
55
|
+
"""Display message role with color coding for inline."""
|
56
|
+
role_colors = {
|
57
|
+
'user': '#2563eb',
|
58
|
+
'assistant': '#059669',
|
59
|
+
'system': '#7c3aed'
|
60
|
+
}
|
61
|
+
color = role_colors.get(obj.role, '#6b7280')
|
62
|
+
return format_html(
|
63
|
+
'<span style="background-color: {}; color: white; padding: 1px 6px; '
|
64
|
+
'border-radius: 8px; font-size: 11px; font-weight: 500;">{}</span>',
|
65
|
+
color,
|
66
|
+
obj.role.upper()
|
67
|
+
)
|
68
|
+
|
69
|
+
@display(description="Content Preview")
|
70
|
+
def content_preview_inline(self, obj):
|
71
|
+
"""Shortened content preview for inline display."""
|
72
|
+
if not obj.content:
|
73
|
+
return "-"
|
74
|
+
preview = obj.content[:80] + "..." if len(obj.content) > 80 else obj.content
|
75
|
+
return format_html(
|
76
|
+
'<div style="max-width: 250px; font-size: 12px; color: #666; '
|
77
|
+
'font-family: monospace;">{}</div>',
|
78
|
+
preview
|
79
|
+
)
|
80
|
+
|
81
|
+
@display(description="Cost (USD)")
|
82
|
+
def cost_display_inline(self, obj):
|
83
|
+
"""Display cost with currency formatting for inline."""
|
84
|
+
return f"${obj.cost_usd:.6f}"
|
85
|
+
|
86
|
+
@display(description="Time")
|
87
|
+
def processing_time_inline(self, obj):
|
88
|
+
"""Display processing time in compact format for inline."""
|
89
|
+
ms = obj.processing_time_ms
|
90
|
+
if ms < 1000:
|
91
|
+
return f"{ms}ms"
|
92
|
+
else:
|
93
|
+
seconds = ms / 1000
|
94
|
+
return f"{seconds:.1f}s"
|
95
|
+
|
96
|
+
def get_queryset(self, request):
|
97
|
+
"""Optimize queryset for inline display."""
|
98
|
+
return super().get_queryset(request).select_related('session', 'user').order_by('created_at')
|
99
|
+
|
100
|
+
|
101
|
+
@admin.register(ChatSession)
|
102
|
+
class ChatSessionAdmin(ModelAdmin):
|
103
|
+
"""Admin interface for ChatSession model with Unfold styling."""
|
104
|
+
|
105
|
+
list_display = [
|
106
|
+
'short_uuid', 'title_display', 'user', 'status_badge', 'messages_count',
|
107
|
+
'tokens_display', 'cost_display', 'model_name', 'created_at'
|
108
|
+
]
|
109
|
+
ordering = ['-created_at'] # Newest first
|
110
|
+
inlines = [ChatMessageInline]
|
111
|
+
list_filter = [
|
112
|
+
'is_active', 'model_name', 'created_at',
|
113
|
+
('user', AutocompleteSelectFilter)
|
114
|
+
]
|
115
|
+
search_fields = ['title', 'user__username', 'user__email']
|
116
|
+
autocomplete_fields = ['user']
|
117
|
+
readonly_fields = [
|
118
|
+
'id', 'messages_count', 'total_tokens_used', 'total_cost_usd',
|
119
|
+
'created_at', 'updated_at', 'avg_tokens_per_message'
|
120
|
+
]
|
121
|
+
|
122
|
+
fieldsets = (
|
123
|
+
('Basic Information', {
|
124
|
+
'fields': ('id', 'title', 'user', 'is_active')
|
125
|
+
}),
|
126
|
+
('Configuration', {
|
127
|
+
'fields': ('model_name', 'temperature', 'max_context_chunks')
|
128
|
+
}),
|
129
|
+
('Statistics', {
|
130
|
+
'fields': (
|
131
|
+
'messages_count', 'total_tokens_used', 'total_cost_usd',
|
132
|
+
'avg_tokens_per_message'
|
133
|
+
)
|
134
|
+
}),
|
135
|
+
('Timestamps', {
|
136
|
+
'fields': ('created_at', 'updated_at'),
|
137
|
+
'classes': ('collapse',)
|
138
|
+
})
|
139
|
+
)
|
140
|
+
|
141
|
+
# Unfold configuration
|
142
|
+
compressed_fields = True
|
143
|
+
warn_unsaved_form = True
|
144
|
+
|
145
|
+
def get_queryset(self, request):
|
146
|
+
"""Optimize queryset with select_related."""
|
147
|
+
return super().get_queryset(request).select_related('user')
|
148
|
+
|
149
|
+
@display(description="Session Title", ordering="title")
|
150
|
+
def title_display(self, obj):
|
151
|
+
"""Display session title with truncation."""
|
152
|
+
title = obj.title or "Untitled Session"
|
153
|
+
if len(title) > 50:
|
154
|
+
title = title[:47] + "..."
|
155
|
+
return format_html(
|
156
|
+
'<div style="font-weight: 500;">{}</div>',
|
157
|
+
title
|
158
|
+
)
|
159
|
+
|
160
|
+
@display(description="Status", ordering="is_active")
|
161
|
+
def status_badge(self, obj):
|
162
|
+
"""Display session status with color coding."""
|
163
|
+
if obj.is_active:
|
164
|
+
return format_html(
|
165
|
+
'<span style="color: green; font-weight: bold;">● Active</span>'
|
166
|
+
)
|
167
|
+
return format_html(
|
168
|
+
'<span style="color: gray; font-weight: bold;">● Inactive</span>'
|
169
|
+
)
|
170
|
+
|
171
|
+
@display(description="Tokens", ordering="total_tokens_used")
|
172
|
+
def tokens_display(self, obj):
|
173
|
+
"""Display token usage with formatting."""
|
174
|
+
tokens = obj.total_tokens_used
|
175
|
+
if tokens > 1000:
|
176
|
+
return f"{tokens/1000:.1f}K"
|
177
|
+
return str(tokens)
|
178
|
+
|
179
|
+
@display(description="Cost (USD)", ordering="total_cost_usd")
|
180
|
+
def cost_display(self, obj):
|
181
|
+
"""Display cost with currency formatting."""
|
182
|
+
return f"${obj.total_cost_usd:.6f}"
|
183
|
+
|
184
|
+
@display(description="Avg Tokens/Message")
|
185
|
+
def avg_tokens_per_message(self, obj):
|
186
|
+
"""Calculate average tokens per message."""
|
187
|
+
if obj.messages_count > 0:
|
188
|
+
avg = obj.total_tokens_used / obj.messages_count
|
189
|
+
return f"{avg:.0f}"
|
190
|
+
return "0"
|
191
|
+
|
192
|
+
def changelist_view(self, request, extra_context=None):
|
193
|
+
"""Add session statistics to changelist."""
|
194
|
+
extra_context = extra_context or {}
|
195
|
+
|
196
|
+
queryset = self.get_queryset(request)
|
197
|
+
stats = queryset.aggregate(
|
198
|
+
total_sessions=Count('id'),
|
199
|
+
active_sessions=Count('id', filter=Q(is_active=True)),
|
200
|
+
total_messages=Sum('messages_count'),
|
201
|
+
total_tokens=Sum('total_tokens_used'),
|
202
|
+
total_cost=Sum('total_cost_usd'),
|
203
|
+
avg_messages_per_session=Avg('messages_count')
|
204
|
+
)
|
205
|
+
|
206
|
+
# Model breakdown
|
207
|
+
model_counts = dict(
|
208
|
+
queryset.values_list('model_name').annotate(
|
209
|
+
count=Count('id')
|
210
|
+
)
|
211
|
+
)
|
212
|
+
|
213
|
+
extra_context['session_stats'] = {
|
214
|
+
'total_sessions': stats['total_sessions'] or 0,
|
215
|
+
'active_sessions': stats['active_sessions'] or 0,
|
216
|
+
'total_messages': stats['total_messages'] or 0,
|
217
|
+
'total_tokens': stats['total_tokens'] or 0,
|
218
|
+
'total_cost': f"${(stats['total_cost'] or 0):.6f}",
|
219
|
+
'avg_messages_per_session': f"{(stats['avg_messages_per_session'] or 0):.1f}",
|
220
|
+
'model_counts': model_counts
|
221
|
+
}
|
222
|
+
|
223
|
+
return super().changelist_view(request, extra_context)
|
224
|
+
|
225
|
+
|
226
|
+
@admin.register(ChatMessage)
|
227
|
+
class ChatMessageAdmin(ModelAdmin):
|
228
|
+
"""Admin interface for ChatMessage model with Unfold styling."""
|
229
|
+
|
230
|
+
list_display = [
|
231
|
+
'short_uuid', 'session_link', 'role_badge', 'content_preview', 'user',
|
232
|
+
'tokens_display', 'cost_display', 'processing_time_display', 'created_at'
|
233
|
+
]
|
234
|
+
ordering = ['-created_at'] # Newest first
|
235
|
+
list_filter = [
|
236
|
+
'role', 'model_name', 'finish_reason', 'created_at',
|
237
|
+
('user', AutocompleteSelectFilter),
|
238
|
+
('session', AutocompleteSelectFilter)
|
239
|
+
]
|
240
|
+
search_fields = ['session__title', 'user__username', 'content']
|
241
|
+
readonly_fields = [
|
242
|
+
'id', 'tokens_used', 'cost_usd', 'processing_time_ms',
|
243
|
+
'created_at', 'updated_at', 'content_stats'
|
244
|
+
]
|
245
|
+
|
246
|
+
fieldsets = (
|
247
|
+
('Basic Information', {
|
248
|
+
'fields': ('id', 'session', 'user', 'role')
|
249
|
+
}),
|
250
|
+
('Content', {
|
251
|
+
'fields': ('content', 'content_stats')
|
252
|
+
}),
|
253
|
+
('Context', {
|
254
|
+
'fields': ('context_chunks',),
|
255
|
+
'classes': ('collapse',)
|
256
|
+
}),
|
257
|
+
('Usage Statistics', {
|
258
|
+
'fields': ('tokens_used', 'cost_usd', 'processing_time_ms')
|
259
|
+
}),
|
260
|
+
('Response Metadata', {
|
261
|
+
'fields': ('model_name', 'finish_reason'),
|
262
|
+
'classes': ('collapse',)
|
263
|
+
}),
|
264
|
+
('Timestamps', {
|
265
|
+
'fields': ('created_at', 'updated_at'),
|
266
|
+
'classes': ('collapse',)
|
267
|
+
})
|
268
|
+
)
|
269
|
+
|
270
|
+
# Unfold configuration
|
271
|
+
compressed_fields = True
|
272
|
+
warn_unsaved_form = True
|
273
|
+
|
274
|
+
def get_queryset(self, request):
|
275
|
+
"""Optimize queryset with select_related."""
|
276
|
+
return super().get_queryset(request).select_related('session', 'user')
|
277
|
+
|
278
|
+
@display(description="Session", ordering="session__title")
|
279
|
+
def session_link(self, obj):
|
280
|
+
"""Display session title with admin link."""
|
281
|
+
url = reverse('admin:django_cfg_knowbase_chatsession_change', args=[obj.session.id])
|
282
|
+
title = obj.session.title or "Untitled Session"
|
283
|
+
if len(title) > 30:
|
284
|
+
title = title[:27] + "..."
|
285
|
+
return format_html(
|
286
|
+
'<a href="{}" style="text-decoration: none;">{}</a>',
|
287
|
+
url,
|
288
|
+
title
|
289
|
+
)
|
290
|
+
|
291
|
+
@display(description="Role", ordering="role")
|
292
|
+
def role_badge(self, obj):
|
293
|
+
"""Display message role with color coding."""
|
294
|
+
role_colors = {
|
295
|
+
'user': '#2563eb',
|
296
|
+
'assistant': '#059669',
|
297
|
+
'system': '#7c3aed'
|
298
|
+
}
|
299
|
+
color = role_colors.get(obj.role, '#6b7280')
|
300
|
+
return format_html(
|
301
|
+
'<span style="background-color: {}; color: white; padding: 2px 8px; '
|
302
|
+
'border-radius: 12px; font-size: 12px; font-weight: 500;">{}</span>',
|
303
|
+
color,
|
304
|
+
obj.role.upper()
|
305
|
+
)
|
306
|
+
|
307
|
+
@display(description="Content Preview")
|
308
|
+
def content_preview(self, obj):
|
309
|
+
"""Display content preview with truncation."""
|
310
|
+
preview = obj.content[:100] + "..." if len(obj.content) > 100 else obj.content
|
311
|
+
return format_html(
|
312
|
+
'<div style="max-width: 300px; word-wrap: break-word; '
|
313
|
+
'font-family: monospace; font-size: 13px;">{}</div>',
|
314
|
+
preview
|
315
|
+
)
|
316
|
+
|
317
|
+
@display(description="Tokens", ordering="tokens_used")
|
318
|
+
def tokens_display(self, obj):
|
319
|
+
"""Display token usage with formatting."""
|
320
|
+
tokens = obj.tokens_used
|
321
|
+
if tokens > 1000:
|
322
|
+
return f"{tokens/1000:.1f}K"
|
323
|
+
return str(tokens)
|
324
|
+
|
325
|
+
@display(description="Cost (USD)", ordering="cost_usd")
|
326
|
+
def cost_display(self, obj):
|
327
|
+
"""Display cost with currency formatting."""
|
328
|
+
return f"${obj.cost_usd:.6f}"
|
329
|
+
|
330
|
+
@display(description="Processing Time", ordering="processing_time_ms")
|
331
|
+
def processing_time_display(self, obj):
|
332
|
+
"""Display processing time in readable format."""
|
333
|
+
ms = obj.processing_time_ms
|
334
|
+
if ms < 1000:
|
335
|
+
return f"{ms}ms"
|
336
|
+
else:
|
337
|
+
seconds = ms / 1000
|
338
|
+
return f"{seconds:.1f}s"
|
339
|
+
|
340
|
+
@display(description="Content Statistics")
|
341
|
+
def content_stats(self, obj):
|
342
|
+
"""Display content statistics."""
|
343
|
+
char_count = len(obj.content)
|
344
|
+
word_count = len(obj.content.split())
|
345
|
+
return format_html(
|
346
|
+
'<div style="font-size: 12px; color: #6b7280;">'
|
347
|
+
'Characters: {} | Words: {} | Context Chunks: {}</div>',
|
348
|
+
char_count,
|
349
|
+
word_count,
|
350
|
+
len(obj.context_chunks)
|
351
|
+
)
|
352
|
+
|
353
|
+
def changelist_view(self, request, extra_context=None):
|
354
|
+
"""Add message statistics to changelist."""
|
355
|
+
extra_context = extra_context or {}
|
356
|
+
|
357
|
+
queryset = self.get_queryset(request)
|
358
|
+
stats = queryset.aggregate(
|
359
|
+
total_messages=Count('id'),
|
360
|
+
user_messages=Count('id', filter=Q(role='user')),
|
361
|
+
assistant_messages=Count('id', filter=Q(role='assistant')),
|
362
|
+
total_tokens=Sum('tokens_used'),
|
363
|
+
total_cost=Sum('cost_usd'),
|
364
|
+
avg_processing_time=Avg('processing_time_ms'),
|
365
|
+
avg_tokens_per_message=Avg('tokens_used')
|
366
|
+
)
|
367
|
+
|
368
|
+
# Role breakdown
|
369
|
+
role_counts = dict(
|
370
|
+
queryset.values_list('role').annotate(
|
371
|
+
count=Count('id')
|
372
|
+
)
|
373
|
+
)
|
374
|
+
|
375
|
+
extra_context['message_stats'] = {
|
376
|
+
'total_messages': stats['total_messages'] or 0,
|
377
|
+
'user_messages': stats['user_messages'] or 0,
|
378
|
+
'assistant_messages': stats['assistant_messages'] or 0,
|
379
|
+
'total_tokens': stats['total_tokens'] or 0,
|
380
|
+
'total_cost': f"${(stats['total_cost'] or 0):.6f}",
|
381
|
+
'avg_processing_time': f"{(stats['avg_processing_time'] or 0):.0f}ms",
|
382
|
+
'avg_tokens_per_message': f"{(stats['avg_tokens_per_message'] or 0):.0f}",
|
383
|
+
'role_counts': role_counts
|
384
|
+
}
|
385
|
+
|
386
|
+
return super().changelist_view(request, extra_context)
|