diff --git a/server/apps/research/admin/article_admin.py b/server/apps/research/admin/article_admin.py index 91b556b..ef090fb 100644 --- a/server/apps/research/admin/article_admin.py +++ b/server/apps/research/admin/article_admin.py @@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['acknowledgement'].widget = TinyMCE(attrs={'cols': 80, 'rows': 30, 'id': "acknowledgement_richtext_field", 'placeholder': f"Enter Acknowledgement here"}) self.fields['content'].widget = TinyMCE(attrs={'cols': 80, 'rows': 30, 'id': "content_richtext_field", 'placeholder': f"Enter Article Content here"}) - self.fields['summary'].widget = TinyMCE(attrs={'cols': 80, 'rows': 30, 'id': "summary_richtext_field", 'placeholder': f"Article summary will be generated here"}) + self.fields['gpt_summary'].widget = TinyMCE(attrs={'cols': 80, 'rows': 15, 'id': "gpt_summary_richtext_field", 'placeholder': f"GPT-generated summary will appear here"}) class ArticleAdmin(admin.ModelAdmin): """Admin interface for the Article model.""" @@ -42,7 +42,7 @@ async def _generate_summary(self, content: str) -> str: "to fully understand the piece without needing to read the original. Your summary should:\n" "- Provide enough depth and detail so the user gets a complete understanding of the core ideas.\n" "- Be in HTML format, use

tags for headings if needed. Avoid other heading levels.\n" - "- Minimize the use of bullet points. If you need to list items, you can, but prefer concise paragraph formatting.\n" + "- Minimize the use of bullet points. If you need to list items, you can, but prefer concise paragraph formatting.\n\n" ) return await self.gpt_service.prompt(system_prompt, content) @@ -51,8 +51,8 @@ def generate_summary_view(self, request): content = request.POST.get('content') try: # Run the async function in the sync view - summary = asyncio.run(self._generate_summary(content)) - return JsonResponse({'summary': summary}) + gpt_summary = asyncio.run(self._generate_summary(content)) + return JsonResponse({'summary': gpt_summary}) except Exception as e: return JsonResponse({'error': str(e)}, status=500) return JsonResponse({'error': 'Invalid request method'}, status=400) @@ -62,7 +62,7 @@ def current_slug_history(self, obj): current_slug_history.short_description = 'Slug Change History' fieldsets = [ - ('Article Details', {'fields': ['title', 'slug', 'authors', 'acknowledgement', 'categories', 'thumb', 'content', 'summary', 'status', 'scheduled_publish_time']}), + ('Article Details', {'fields': ['title', 'slug', 'authors', 'acknowledgement', 'categories', 'thumb', 'content', 'summary', 'gpt_summary', 'status', 'scheduled_publish_time']}), ('Sponsorship Details', {'fields': ['is_sponsored', 'sponsor_color', 'sponsor_text_color']}), ('URL Management', { 'fields': ('current_slug_history',), diff --git a/server/apps/research/migrations/0017_article_gpt_summary_alter_article_summary.py b/server/apps/research/migrations/0017_article_gpt_summary_alter_article_summary.py index af3fb92..9d2b209 100644 --- a/server/apps/research/migrations/0017_article_gpt_summary_alter_article_summary.py +++ b/server/apps/research/migrations/0017_article_gpt_summary_alter_article_summary.py @@ -1,8 +1,7 @@ # Generated by Django 5.0.8 on 2024-12-10 18:18 import tinymce.models -from django.db import migrations, models - +from django.db import migrations class Migration(migrations.Migration): @@ -14,11 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='article', name='gpt_summary', - field=models.TextField(blank=True, null=True), - ), - migrations.AlterField( - model_name='article', - name='summary', field=tinymce.models.HTMLField(blank=True, null=True), ), ] diff --git a/server/apps/research/models/article.py b/server/apps/research/models/article.py index a4d3c1b..5d8883e 100644 --- a/server/apps/research/models/article.py +++ b/server/apps/research/models/article.py @@ -25,7 +25,7 @@ class Article(BaseModel): title = models.TextField() content = HTMLField(blank=True, null=True) - summary = HTMLField(blank=True, null=True) + summary = models.TextField(blank=True) gpt_summary = models.TextField(blank=True, null=True) acknowledgement = HTMLField(blank=True, null=True) authors = models.ManyToManyField(Author, blank=True, related_name='articles') diff --git a/server/static/css/article_admin.css b/server/static/css/article_admin.css index c0a67d4..fef5266 100644 --- a/server/static/css/article_admin.css +++ b/server/static/css/article_admin.css @@ -1,5 +1,5 @@ .generate-summary-btn { - background-color: #417690; + background-color: #0C4B33; color: white; padding: 10px 15px; border: none; @@ -7,10 +7,7 @@ cursor: pointer; font-weight: bold; margin-left: 10px; -} - -.generate-summary-btn:hover { - background-color: #2b5172; + align-self: baseline; } .generate-summary-btn:disabled { @@ -22,4 +19,10 @@ margin-left: 10px; font-style: italic; color: #666; +} + +.generate-summary-container-btn { + display: flex; + flex-direction: column; + gap: 10px; } \ No newline at end of file diff --git a/server/static/js/article_admin.js b/server/static/js/article_admin.js index ef9aee9..4e28844 100644 --- a/server/static/js/article_admin.js +++ b/server/static/js/article_admin.js @@ -1,57 +1,56 @@ document.addEventListener('DOMContentLoaded', function() { - // Add generate summary button after the content field - const contentField = document.getElementById('content_richtext_field'); - const summaryField = document.getElementById('summary_richtext_field'); - if (contentField) { - const button = document.createElement('button'); - button.type = 'button'; - button.id = 'generate-summary-btn'; - button.className = 'generate-summary-btn'; - button.textContent = 'Generate Summary'; - button.style.height = 'min-content'; + // Wait for TinyMCE to initialize + if (typeof tinymce !== 'undefined') { + const gptSummaryContainer = document.getElementById('gpt_summary_richtext_field'); + if (gptSummaryContainer) { + const buttonContainer = document.createElement('div'); + buttonContainer.id = 'generate-summary-container-btn'; - const statusSpan = document.createElement('span'); - statusSpan.className = 'summary-status'; - statusSpan.id = 'summary-status'; + const button = document.createElement('button'); + button.type = 'button'; + button.id = 'generate-summary-btn'; + button.className = 'generate-summary-btn'; + button.textContent = 'Generate Summary'; - summaryField.parentNode.insertBefore(button, summaryField.nextSibling); - button.parentNode.insertBefore(statusSpan, button.nextSibling); + const statusSpan = document.createElement('p'); + statusSpan.className = 'summary-status'; + statusSpan.id = 'summary-status'; - // Add click event listener to the button - button.addEventListener('click', function() { - const editor = tinymce.get('content_richtext_field'); - const summaryEditor = tinymce.get('summary_richtext_field'); - - if (editor && summaryEditor) { - const content = editor.getContent(); - if (!content.trim()) { - alert('Please enter some content before generating a summary.'); - return; - } + buttonContainer.appendChild(button); + buttonContainer.appendChild(statusSpan); + + gptSummaryContainer.parentNode.insertBefore(buttonContainer, gptSummaryContainer.nextSibling); + button.parentNode.insertBefore(statusSpan, button.nextSibling); - // Disable both editors and the button - editor.setMode('readonly'); - summaryEditor.setMode('readonly'); - button.disabled = true; + button.addEventListener('click', function() { + const contentEditor = tinymce.get('content_richtext_field'); + const gptSummaryContainer = document.getElementById('gpt_summary_richtext_field'); statusSpan.textContent = ' Generating summary...'; + + if (contentEditor && gptSummaryContainer) { + const content = contentEditor.getContent(); + if (!content.trim()) { + alert('Please enter some content before generating a summary.'); + return; + } - // Call the GPT API - generateSummary(content) - .then(() => { - statusSpan.textContent = ' Summary generated successfully!'; - }) - .catch(error => { - console.error('Error generating summary:', error); - statusSpan.textContent = ' Error generating summary. Please try again.'; - }) - .finally(() => { - // Re-enable both editors and the button - editor.setMode('design'); - summaryEditor.setMode('design'); - button.disabled = false; - }); - } - }); + // Call the GPT API + generateSummary(content) + .then(() => { + statusSpan.textContent = ' Summary generated successfully!'; + }) + .catch(error => { + statusSpan.textContent = ' Error generating summary. Please try again.'; + }) + .finally(() => { + // Re-enable editors and button + contentEditor.setMode('design'); + gptSummaryEditor.setMode('design'); + button.disabled = false; + }); + } + }); + } } }); @@ -74,7 +73,7 @@ async function generateSummary(content) { // Update the summary field if (typeof tinymce !== 'undefined') { - const summaryEditor = tinymce.get('summary_richtext_field'); + const summaryEditor = tinymce.get('gpt_summary_richtext_field'); if (summaryEditor) { summaryEditor.setContent(data.summary); }