You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

160 lines
6.5 KiB

from django.contrib import admin
from django.db import models
from django.shortcuts import redirect
from django.urls import reverse
# 1. Change TabularInline to StackedInline
from unfold.admin import ModelAdmin, TabularInline
from utils.admin import dovoodi_admin_site, project_admin_site
from .models import AgentSettings, AgentPrompt, EmbeddingSession
from django.utils.html import format_html
import requests
import threading
class AgentPromptInline(TabularInline):
model = AgentPrompt
extra = 0
fields = ('is_active', 'content')
formfield_overrides = {
models.TextField: {
'widget': admin.widgets.AdminTextareaWidget(attrs={
# 1. HEIGHT: Set to 1 row to keep it compact (it will expand if they type)
'rows': 2,
'class': (
'border border-gray-300 rounded-md shadow-sm '
'w-full block sm:text-sm '
'bg-white text-gray-900 '
'dark:bg-gray-900 dark:text-white dark:border-gray-700 '
'focus:ring-primary-500 focus:border-primary-500'
),
# 2. SIZE FIX:
# - background/color: inherit to keep your color fix
# - min-width: 600px to force the table column to be wide
# - width: 100% to fill that 600px+ space
'style': (
'background-color: inherit; '
'color: inherit; '
'width: 100%; '
'min-width: 700px; ' # Increased slightly for even better UX
'resize: vertical;'
)
})
},
}
class AgentSettingsAdmin(ModelAdmin):
# ... keep your existing permission and redirect logic ...
def has_add_permission(self, request): return False
def has_delete_permission(self, request, obj=None): return False
def changelist_view(self, request, extra_context=None):
obj, created = self.model.objects.get_or_create(pk=1)
url = reverse(
f"{self.admin_site.name}:{self.model._meta.app_label}_{self.model._meta.model_name}_change",
args=[obj.pk]
)
return redirect(url)
# 2. Add the Stacked Inline
inlines = [AgentPromptInline]
class EmbeddingSessionAdmin(ModelAdmin):
# What to show in the table view
list_display = ['id', 'status_badge', 'progress_bar', 'created_at']
# We make everything read-only so admins can't fake the progress
readonly_fields = ['status', 'progress', 'processed_items', 'total_items', 'error_message']
# Optional: If you want to customize how the error message text area looks in Unfold
formfield_overrides = {
models.TextField: {
'widget': admin.widgets.AdminTextareaWidget(attrs={
'rows': 4,
'class': (
'border border-gray-300 rounded-md shadow-sm '
'w-full block sm:text-sm '
'bg-white text-gray-900 '
'dark:bg-gray-900 dark:text-white dark:border-gray-700 '
'focus:ring-primary-500 focus:border-primary-500'
),
'style': 'background-color: inherit; color: inherit; width: 100%;'
})
},
}
@admin.display(description="Status")
def status_badge(self, obj):
"""Creates a beautiful Unfold/Tailwind badge based on the status."""
colors = {
'PENDING': 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300',
'PROCESSING': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300',
'COMPLETED': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300',
'FAILED': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300',
}
color_classes = colors.get(obj.status, colors['PENDING'])
return format_html(
'<span class="px-2.5 py-1 inline-flex text-xs leading-5 font-semibold rounded-full {}">'
'{}'
'</span>',
color_classes,
obj.status
)
@admin.display(description="Progress")
def progress_bar(self, obj):
"""Creates a Tailwind progress bar to show sync status with percentages."""
if obj.status == 'PENDING':
return format_html('<span class="text-sm text-gray-500 dark:text-gray-400">Waiting to start...</span>')
return format_html(
'''
<div class="flex flex-col gap-1 w-64">
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
<div class="bg-primary-600 h-2 rounded-full transition-all duration-500" style="width: {}%"></div>
</div>
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400">
<span class="font-bold text-gray-700 dark:text-gray-300">{}%</span>
<span>{} / {} items</span>
</div>
</div>
''',
obj.progress, # For the CSS width inside the style="" tag
obj.progress, # For the text percentage display
obj.processed_items, # For the items count
obj.total_items # For the total count
)
def save_model(self, request, obj, form, change):
"""
Intercept the save. If it's a new record, tell the FastAPI agent to start working.
"""
is_new = obj.pk is None
super().save_model(request, obj, form, change)
if is_new:
# 🟢 Trigger the FastAPI Agent in the background
def fire_and_forget():
try:
# Point this to your FastAPI Agent URL
# Make sure host.docker.internal works, or use the agent's container name
requests.post(
"http://127.0.0.1:8081/api/sync-knowledge",
json={"session_id": obj.id},
timeout=5
)
except Exception as e:
print(f"Failed to trigger agent: {e}")
threading.Thread(target=fire_and_forget).start()
# Register to your custom Unfold admin sites
dovoodi_admin_site.register(EmbeddingSession, EmbeddingSessionAdmin)
project_admin_site.register(EmbeddingSession, EmbeddingSessionAdmin)
dovoodi_admin_site.register(AgentSettings, AgentSettingsAdmin)
project_admin_site.register(AgentSettings, AgentSettingsAdmin)