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( '' '{}' '', 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('Waiting to start...') return format_html( '''
{}% {} / {} items
''', 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)