django-cfg 1.1.81__py3-none-any.whl → 1.2.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_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 +444 -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 +79 -17
- 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/templates/guide.md +266 -0
- 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.81.dist-info → django_cfg-1.2.0.dist-info}/METADATA +83 -86
- django_cfg-1.2.0.dist-info/RECORD +441 -0
- django_cfg/apps/tasks/@docs/README.md +0 -195
- 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.81.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.81.dist-info → django_cfg-1.2.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,460 @@
|
|
1
|
+
"""
|
2
|
+
Execution admin interfaces with Unfold optimization.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.contrib import admin, messages
|
6
|
+
from django.utils.html import format_html
|
7
|
+
from django.urls import reverse
|
8
|
+
from django.utils.safestring import mark_safe
|
9
|
+
from django.db import models
|
10
|
+
from django.db.models import Count, Avg, Sum, Q
|
11
|
+
from django.utils import timezone
|
12
|
+
from django.db.models.fields.json import JSONField
|
13
|
+
from datetime import timedelta
|
14
|
+
from django_json_widget.widgets import JSONEditorWidget
|
15
|
+
from unfold.admin import ModelAdmin, TabularInline
|
16
|
+
from unfold.decorators import display, action
|
17
|
+
from unfold.enums import ActionVariant
|
18
|
+
from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
|
19
|
+
from unfold.contrib.forms.widgets import WysiwygWidget
|
20
|
+
|
21
|
+
from ..models.execution import AgentExecution, WorkflowExecution
|
22
|
+
|
23
|
+
|
24
|
+
class AgentExecutionInlineForWorkflow(TabularInline):
|
25
|
+
"""Inline for agent executions within workflow with Unfold styling."""
|
26
|
+
|
27
|
+
model = AgentExecution
|
28
|
+
verbose_name = "Agent Execution"
|
29
|
+
verbose_name_plural = "🔗 Workflow Steps (Read-only)"
|
30
|
+
extra = 0
|
31
|
+
max_num = 0
|
32
|
+
can_delete = False
|
33
|
+
show_change_link = True
|
34
|
+
|
35
|
+
def has_add_permission(self, request, obj=None):
|
36
|
+
return False
|
37
|
+
|
38
|
+
def has_change_permission(self, request, obj=None):
|
39
|
+
return False
|
40
|
+
|
41
|
+
def has_delete_permission(self, request, obj=None):
|
42
|
+
return False
|
43
|
+
|
44
|
+
fields = [
|
45
|
+
'execution_order', 'agent_name', 'status_badge_inline',
|
46
|
+
'execution_time_display', 'tokens_used', 'cost_display_inline'
|
47
|
+
]
|
48
|
+
readonly_fields = [
|
49
|
+
'execution_order', 'agent_name', 'status_badge_inline',
|
50
|
+
'execution_time_display', 'tokens_used', 'cost_display_inline'
|
51
|
+
]
|
52
|
+
|
53
|
+
# Unfold specific options
|
54
|
+
hide_title = False
|
55
|
+
classes = ['collapse']
|
56
|
+
|
57
|
+
@display(description="Status")
|
58
|
+
def status_badge_inline(self, obj):
|
59
|
+
"""Status badge for inline display."""
|
60
|
+
colors = {
|
61
|
+
'pending': 'bg-yellow-100 text-yellow-800',
|
62
|
+
'running': 'bg-blue-100 text-blue-800',
|
63
|
+
'completed': 'bg-green-100 text-green-800',
|
64
|
+
'failed': 'bg-red-100 text-red-800',
|
65
|
+
'cancelled': 'bg-gray-100 text-gray-800'
|
66
|
+
}
|
67
|
+
color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
|
68
|
+
return format_html(
|
69
|
+
'<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
|
70
|
+
color_class, obj.get_status_display()
|
71
|
+
)
|
72
|
+
|
73
|
+
@display(description="Execution Time")
|
74
|
+
def execution_time_display(self, obj):
|
75
|
+
"""Execution time display for inline."""
|
76
|
+
if obj.execution_time:
|
77
|
+
return f"{obj.execution_time:.2f}s"
|
78
|
+
return "-"
|
79
|
+
|
80
|
+
@display(description="Cost")
|
81
|
+
def cost_display_inline(self, obj):
|
82
|
+
"""Cost display for inline."""
|
83
|
+
if obj.cost:
|
84
|
+
return f"${obj.cost:.4f}"
|
85
|
+
return "-"
|
86
|
+
|
87
|
+
def get_queryset(self, request):
|
88
|
+
"""Optimize queryset for inline display."""
|
89
|
+
return super().get_queryset(request).select_related('user').order_by('execution_order')
|
90
|
+
|
91
|
+
|
92
|
+
@admin.register(AgentExecution)
|
93
|
+
class AgentExecutionAdmin(ModelAdmin):
|
94
|
+
"""Admin interface for AgentExecution with Unfold styling."""
|
95
|
+
|
96
|
+
list_display = [
|
97
|
+
'id_display', 'agent_name_display', 'status_badge', 'user',
|
98
|
+
'execution_metrics', 'cost_display', 'cached_badge', 'created_at'
|
99
|
+
]
|
100
|
+
ordering = ['-created_at']
|
101
|
+
list_filter = [
|
102
|
+
'status', 'cached', 'agent_name', 'created_at',
|
103
|
+
('user', AutocompleteSelectFilter),
|
104
|
+
('workflow_execution', AutocompleteSelectFilter)
|
105
|
+
]
|
106
|
+
search_fields = ['agent_name', 'user__username', 'input_prompt', 'output_data']
|
107
|
+
autocomplete_fields = ['user', 'workflow_execution']
|
108
|
+
readonly_fields = [
|
109
|
+
'id', 'execution_time', 'tokens_used', 'cost', 'cached',
|
110
|
+
'created_at', 'started_at', 'completed_at', 'duration_display',
|
111
|
+
'input_preview', 'output_preview', 'error_preview'
|
112
|
+
]
|
113
|
+
|
114
|
+
# Unfold form field overrides
|
115
|
+
formfield_overrides = {
|
116
|
+
models.TextField: {"widget": WysiwygWidget},
|
117
|
+
JSONField: {"widget": JSONEditorWidget},
|
118
|
+
}
|
119
|
+
|
120
|
+
fieldsets = (
|
121
|
+
("🚀 Execution Info", {
|
122
|
+
'fields': ('id', 'agent_name', 'user', 'status'),
|
123
|
+
'classes': ('tab',)
|
124
|
+
}),
|
125
|
+
("📝 Input/Output", {
|
126
|
+
'fields': ('input_preview', 'input_prompt', 'output_preview', 'output_data', 'error_preview', 'error_message'),
|
127
|
+
'classes': ('tab',)
|
128
|
+
}),
|
129
|
+
("📊 Metrics", {
|
130
|
+
'fields': ('execution_time', 'tokens_used', 'cost', 'cached'),
|
131
|
+
'classes': ('tab',)
|
132
|
+
}),
|
133
|
+
("🔗 Workflow Context", {
|
134
|
+
'fields': ('workflow_execution', 'execution_order'),
|
135
|
+
'classes': ('tab', 'collapse')
|
136
|
+
}),
|
137
|
+
("⏰ Timestamps", {
|
138
|
+
'fields': ('created_at', 'started_at', 'completed_at', 'duration_display'),
|
139
|
+
'classes': ('tab', 'collapse')
|
140
|
+
}),
|
141
|
+
)
|
142
|
+
|
143
|
+
actions = ['retry_failed_executions', 'clear_cache']
|
144
|
+
|
145
|
+
@display(description="ID")
|
146
|
+
def id_display(self, obj):
|
147
|
+
"""Enhanced ID display."""
|
148
|
+
return format_html(
|
149
|
+
'<span class="font-mono text-sm text-gray-600">#{}</span>',
|
150
|
+
str(obj.id)[:8]
|
151
|
+
)
|
152
|
+
|
153
|
+
@display(description="Agent")
|
154
|
+
def agent_name_display(self, obj):
|
155
|
+
"""Enhanced agent name display."""
|
156
|
+
return format_html(
|
157
|
+
'<div class="flex items-center space-x-2">'
|
158
|
+
'<span class="text-blue-600 font-medium">{}</span>'
|
159
|
+
'</div>',
|
160
|
+
obj.agent_name
|
161
|
+
)
|
162
|
+
|
163
|
+
@display(description="Status")
|
164
|
+
def status_badge(self, obj):
|
165
|
+
"""Status badge with color coding."""
|
166
|
+
colors = {
|
167
|
+
'pending': 'bg-yellow-100 text-yellow-800',
|
168
|
+
'running': 'bg-blue-100 text-blue-800',
|
169
|
+
'completed': 'bg-green-100 text-green-800',
|
170
|
+
'failed': 'bg-red-100 text-red-800',
|
171
|
+
'cancelled': 'bg-gray-100 text-gray-800'
|
172
|
+
}
|
173
|
+
color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
|
174
|
+
return format_html(
|
175
|
+
'<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
|
176
|
+
color_class, obj.get_status_display()
|
177
|
+
)
|
178
|
+
|
179
|
+
@display(description="Metrics")
|
180
|
+
def execution_metrics(self, obj):
|
181
|
+
"""Combined execution metrics."""
|
182
|
+
return format_html(
|
183
|
+
'<div class="text-sm space-y-1">'
|
184
|
+
'<div><span class="font-medium">Time:</span> {}</div>'
|
185
|
+
'<div><span class="font-medium">Tokens:</span> {}</div>'
|
186
|
+
'</div>',
|
187
|
+
f"{obj.execution_time:.2f}s" if obj.execution_time else "-",
|
188
|
+
f"{obj.tokens_used:,}" if obj.tokens_used else "-"
|
189
|
+
)
|
190
|
+
|
191
|
+
@display(description="Cost")
|
192
|
+
def cost_display(self, obj):
|
193
|
+
"""Cost display with formatting."""
|
194
|
+
if obj.cost:
|
195
|
+
return format_html(
|
196
|
+
'<span class="font-mono text-green-600">${:.4f}</span>',
|
197
|
+
obj.cost
|
198
|
+
)
|
199
|
+
return "-"
|
200
|
+
|
201
|
+
@display(description="Cached", boolean=True)
|
202
|
+
def cached_badge(self, obj):
|
203
|
+
"""Cached status badge."""
|
204
|
+
return obj.cached
|
205
|
+
|
206
|
+
@display(description="Duration")
|
207
|
+
def duration_display(self, obj):
|
208
|
+
"""Display execution duration."""
|
209
|
+
if obj.duration:
|
210
|
+
return f"{obj.duration:.2f}s"
|
211
|
+
return "-"
|
212
|
+
|
213
|
+
@display(description="Input Preview")
|
214
|
+
def input_preview(self, obj):
|
215
|
+
"""Preview of input prompt."""
|
216
|
+
if not obj.input_prompt:
|
217
|
+
return "-"
|
218
|
+
preview = obj.input_prompt[:200] + "..." if len(obj.input_prompt) > 200 else obj.input_prompt
|
219
|
+
return format_html(
|
220
|
+
'<div class="text-sm text-gray-600 max-w-md">{}</div>',
|
221
|
+
preview
|
222
|
+
)
|
223
|
+
|
224
|
+
@display(description="Output Preview")
|
225
|
+
def output_preview(self, obj):
|
226
|
+
"""Preview of output data."""
|
227
|
+
if not obj.output_data:
|
228
|
+
return "-"
|
229
|
+
preview = str(obj.output_data)[:200] + "..." if len(str(obj.output_data)) > 200 else str(obj.output_data)
|
230
|
+
return format_html(
|
231
|
+
'<div class="text-sm text-gray-600 max-w-md">{}</div>',
|
232
|
+
preview
|
233
|
+
)
|
234
|
+
|
235
|
+
@display(description="Error Preview")
|
236
|
+
def error_preview(self, obj):
|
237
|
+
"""Preview of error message."""
|
238
|
+
if not obj.error_message:
|
239
|
+
return "-"
|
240
|
+
preview = obj.error_message[:200] + "..." if len(obj.error_message) > 200 else obj.error_message
|
241
|
+
return format_html(
|
242
|
+
'<div class="text-sm text-red-600 max-w-md">{}</div>',
|
243
|
+
preview
|
244
|
+
)
|
245
|
+
|
246
|
+
@action(description="Retry failed executions", icon="refresh", variant=ActionVariant.WARNING)
|
247
|
+
def retry_failed_executions(self, request, queryset):
|
248
|
+
"""Retry failed executions."""
|
249
|
+
failed_count = queryset.filter(status='failed').count()
|
250
|
+
messages.warning(request, f"Retry functionality not implemented yet. {failed_count} failed executions selected.")
|
251
|
+
|
252
|
+
@action(description="Clear cache", icon="clear", variant=ActionVariant.INFO)
|
253
|
+
def clear_cache(self, request, queryset):
|
254
|
+
"""Clear cache for selected executions."""
|
255
|
+
cached_count = queryset.filter(cached=True).count()
|
256
|
+
messages.info(request, f"Cache clearing not implemented yet. {cached_count} cached executions selected.")
|
257
|
+
|
258
|
+
def get_queryset(self, request):
|
259
|
+
"""Optimize queryset."""
|
260
|
+
return super().get_queryset(request).select_related('user', 'workflow_execution')
|
261
|
+
|
262
|
+
|
263
|
+
@admin.register(WorkflowExecution)
|
264
|
+
class WorkflowExecutionAdmin(ModelAdmin):
|
265
|
+
"""Admin interface for WorkflowExecution with Unfold styling."""
|
266
|
+
|
267
|
+
list_display = [
|
268
|
+
'id_display', 'name_display', 'pattern_badge', 'status_badge', 'user',
|
269
|
+
'progress_display', 'metrics_display', 'cost_display', 'created_at'
|
270
|
+
]
|
271
|
+
ordering = ['-created_at']
|
272
|
+
inlines = [AgentExecutionInlineForWorkflow]
|
273
|
+
list_filter = [
|
274
|
+
'status', 'pattern', 'created_at',
|
275
|
+
('user', AutocompleteSelectFilter)
|
276
|
+
]
|
277
|
+
search_fields = ['name', 'user__username', 'input_prompt', 'final_output']
|
278
|
+
autocomplete_fields = ['user']
|
279
|
+
readonly_fields = [
|
280
|
+
'id', 'total_execution_time', 'total_tokens_used', 'total_cost',
|
281
|
+
'created_at', 'started_at', 'completed_at', 'duration_display',
|
282
|
+
'progress_percentage', 'input_preview', 'output_preview', 'error_preview'
|
283
|
+
]
|
284
|
+
|
285
|
+
# Unfold form field overrides
|
286
|
+
formfield_overrides = {
|
287
|
+
models.TextField: {"widget": WysiwygWidget},
|
288
|
+
JSONField: {"widget": JSONEditorWidget},
|
289
|
+
}
|
290
|
+
|
291
|
+
fieldsets = (
|
292
|
+
("🔄 Workflow Info", {
|
293
|
+
'fields': ('id', 'name', 'user', 'pattern', 'status'),
|
294
|
+
'classes': ('tab',)
|
295
|
+
}),
|
296
|
+
("⚙️ Configuration", {
|
297
|
+
'fields': ('agent_names', 'input_preview', 'input_prompt', 'config'),
|
298
|
+
'classes': ('tab',)
|
299
|
+
}),
|
300
|
+
("📈 Progress", {
|
301
|
+
'fields': ('current_step', 'total_steps', 'progress_percentage'),
|
302
|
+
'classes': ('tab',)
|
303
|
+
}),
|
304
|
+
("📋 Results", {
|
305
|
+
'fields': ('output_preview', 'final_output', 'error_preview', 'error_message'),
|
306
|
+
'classes': ('tab',)
|
307
|
+
}),
|
308
|
+
("📊 Metrics", {
|
309
|
+
'fields': ('total_execution_time', 'total_tokens_used', 'total_cost'),
|
310
|
+
'classes': ('tab',)
|
311
|
+
}),
|
312
|
+
("⏰ Timestamps", {
|
313
|
+
'fields': ('created_at', 'started_at', 'completed_at', 'duration_display'),
|
314
|
+
'classes': ('tab', 'collapse')
|
315
|
+
}),
|
316
|
+
)
|
317
|
+
|
318
|
+
actions = ['cancel_running_workflows', 'retry_failed_workflows']
|
319
|
+
|
320
|
+
@display(description="ID")
|
321
|
+
def id_display(self, obj):
|
322
|
+
"""Enhanced ID display."""
|
323
|
+
return format_html(
|
324
|
+
'<span class="font-mono text-sm text-gray-600">#{}</span>',
|
325
|
+
str(obj.id)[:8]
|
326
|
+
)
|
327
|
+
|
328
|
+
@display(description="Workflow")
|
329
|
+
def name_display(self, obj):
|
330
|
+
"""Enhanced workflow name display."""
|
331
|
+
return format_html(
|
332
|
+
'<div class="flex items-center space-x-2">'
|
333
|
+
'<span class="text-indigo-600 font-medium">{}</span>'
|
334
|
+
'</div>',
|
335
|
+
obj.name
|
336
|
+
)
|
337
|
+
|
338
|
+
@display(description="Pattern")
|
339
|
+
def pattern_badge(self, obj):
|
340
|
+
"""Pattern badge."""
|
341
|
+
colors = {
|
342
|
+
'sequential': 'bg-blue-100 text-blue-800',
|
343
|
+
'parallel': 'bg-green-100 text-green-800',
|
344
|
+
'conditional': 'bg-purple-100 text-purple-800',
|
345
|
+
'loop': 'bg-orange-100 text-orange-800'
|
346
|
+
}
|
347
|
+
color_class = colors.get(obj.pattern, 'bg-gray-100 text-gray-800')
|
348
|
+
return format_html(
|
349
|
+
'<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
|
350
|
+
color_class, obj.pattern.title() if obj.pattern else 'Unknown'
|
351
|
+
)
|
352
|
+
|
353
|
+
@display(description="Status")
|
354
|
+
def status_badge(self, obj):
|
355
|
+
"""Status badge with color coding."""
|
356
|
+
colors = {
|
357
|
+
'pending': 'bg-yellow-100 text-yellow-800',
|
358
|
+
'running': 'bg-blue-100 text-blue-800',
|
359
|
+
'completed': 'bg-green-100 text-green-800',
|
360
|
+
'failed': 'bg-red-100 text-red-800',
|
361
|
+
'cancelled': 'bg-gray-100 text-gray-800'
|
362
|
+
}
|
363
|
+
color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
|
364
|
+
return format_html(
|
365
|
+
'<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
|
366
|
+
color_class, obj.get_status_display()
|
367
|
+
)
|
368
|
+
|
369
|
+
@display(description="Progress")
|
370
|
+
def progress_display(self, obj):
|
371
|
+
"""Display progress as a progress bar."""
|
372
|
+
percentage = obj.progress_percentage
|
373
|
+
color_class = 'bg-green-500' if obj.status == 'completed' else 'bg-blue-500' if obj.status == 'running' else 'bg-red-500'
|
374
|
+
|
375
|
+
return format_html(
|
376
|
+
'<div class="w-24 bg-gray-200 rounded-full h-2">'
|
377
|
+
'<div class="h-2 rounded-full {} flex items-center justify-center text-xs text-white" style="width: {}%;">'
|
378
|
+
'</div>'
|
379
|
+
'</div>'
|
380
|
+
'<div class="text-xs text-gray-600 mt-1">{}%</div>',
|
381
|
+
color_class, percentage, int(percentage)
|
382
|
+
)
|
383
|
+
|
384
|
+
@display(description="Metrics")
|
385
|
+
def metrics_display(self, obj):
|
386
|
+
"""Combined metrics display."""
|
387
|
+
return format_html(
|
388
|
+
'<div class="text-sm space-y-1">'
|
389
|
+
'<div><span class="font-medium">Time:</span> {}</div>'
|
390
|
+
'<div><span class="font-medium">Tokens:</span> {}</div>'
|
391
|
+
'</div>',
|
392
|
+
f"{obj.total_execution_time:.2f}s" if obj.total_execution_time else "-",
|
393
|
+
f"{obj.total_tokens_used:,}" if obj.total_tokens_used else "-"
|
394
|
+
)
|
395
|
+
|
396
|
+
@display(description="Cost")
|
397
|
+
def cost_display(self, obj):
|
398
|
+
"""Cost display with formatting."""
|
399
|
+
if obj.total_cost:
|
400
|
+
return format_html(
|
401
|
+
'<span class="font-mono text-green-600">${:.4f}</span>',
|
402
|
+
obj.total_cost
|
403
|
+
)
|
404
|
+
return "-"
|
405
|
+
|
406
|
+
@display(description="Duration")
|
407
|
+
def duration_display(self, obj):
|
408
|
+
"""Display workflow duration."""
|
409
|
+
if obj.duration:
|
410
|
+
return f"{obj.duration:.2f}s"
|
411
|
+
return "-"
|
412
|
+
|
413
|
+
@display(description="Input Preview")
|
414
|
+
def input_preview(self, obj):
|
415
|
+
"""Preview of input prompt."""
|
416
|
+
if not obj.input_prompt:
|
417
|
+
return "-"
|
418
|
+
preview = obj.input_prompt[:200] + "..." if len(obj.input_prompt) > 200 else obj.input_prompt
|
419
|
+
return format_html(
|
420
|
+
'<div class="text-sm text-gray-600 max-w-md">{}</div>',
|
421
|
+
preview
|
422
|
+
)
|
423
|
+
|
424
|
+
@display(description="Output Preview")
|
425
|
+
def output_preview(self, obj):
|
426
|
+
"""Preview of final output."""
|
427
|
+
if not obj.final_output:
|
428
|
+
return "-"
|
429
|
+
preview = str(obj.final_output)[:200] + "..." if len(str(obj.final_output)) > 200 else str(obj.final_output)
|
430
|
+
return format_html(
|
431
|
+
'<div class="text-sm text-gray-600 max-w-md">{}</div>',
|
432
|
+
preview
|
433
|
+
)
|
434
|
+
|
435
|
+
@display(description="Error Preview")
|
436
|
+
def error_preview(self, obj):
|
437
|
+
"""Preview of error message."""
|
438
|
+
if not obj.error_message:
|
439
|
+
return "-"
|
440
|
+
preview = obj.error_message[:200] + "..." if len(obj.error_message) > 200 else obj.error_message
|
441
|
+
return format_html(
|
442
|
+
'<div class="text-sm text-red-600 max-w-md">{}</div>',
|
443
|
+
preview
|
444
|
+
)
|
445
|
+
|
446
|
+
@action(description="Cancel running workflows", icon="stop", variant=ActionVariant.DANGER)
|
447
|
+
def cancel_running_workflows(self, request, queryset):
|
448
|
+
"""Cancel running workflows."""
|
449
|
+
running_count = queryset.filter(status='running').count()
|
450
|
+
messages.warning(request, f"Cancel functionality not implemented yet. {running_count} running workflows selected.")
|
451
|
+
|
452
|
+
@action(description="Retry failed workflows", icon="refresh", variant=ActionVariant.WARNING)
|
453
|
+
def retry_failed_workflows(self, request, queryset):
|
454
|
+
"""Retry failed workflows."""
|
455
|
+
failed_count = queryset.filter(status='failed').count()
|
456
|
+
messages.warning(request, f"Retry functionality not implemented yet. {failed_count} failed workflows selected.")
|
457
|
+
|
458
|
+
def get_queryset(self, request):
|
459
|
+
"""Optimize queryset."""
|
460
|
+
return super().get_queryset(request).select_related('user')
|