본문 바로가기

개인 프로젝트/FakeVerse

[Ai] 가짜 뉴스 생성기 개발 (3)

오늘은 프론트 엔드 구성 및 렌더링까지 해 보겠습니다.
우선 보여지는 것, 즉 views.py를 수정해야 합니다.
 
근데 제가 이전 블로그에서 views.py 코드를 안 올렸더라구요... 죄송 😓
하지만 페이지에 못생기게 뜨는 걸 보면 아시듯이 JSON이 반환되도록 코드를 구성했었습니다.
이제 html을 렌더링해야 하니까 코드를 수정해 줄게요.

from django.shortcuts import render
from django.http import JsonResponse
from celery.result import AsyncResult
from .models import FakeNews  # FakeNews 모델을 만들어야 함
from .tasks import generate_fake_news
import datetime

def main(request):
    return render(request, 'main/main.html')

def generate_news(request):
    # 버튼 클릭 시 가장 최신 뉴스를 즉시 반환, 없으면 Celery 실행
    latest_news = FakeNews.objects.order_by('-created_at').first()

    # 1시간 이내 생성된 뉴스가 있으면 즉시 반환
    if latest_news and (datetime.datetime.now() - latest_news.created_at).seconds < 3600:
        return JsonResponse({'status': 'Completed', 'result': latest_news.content})
    
    # 뉴스가 없거나 너무 오래된 경우, 새로운 뉴스 생성 요청
    task = generate_fake_news.apply_async()
    return JsonResponse({'task_id': task.id})


def generate_news_view(request):
    task = generate_fake_news.apply_async()
    return render(request, 'news/generateNews.html', {'task_id': task.id})


def check_task_status(request, task_id):
    task_result = AsyncResult(task_id)
    if task_result.ready():
        return JsonResponse({'status': 'Completed', 'result': task_result.result})
    else:
        return JsonResponse({'status': 'Pending'})

코드가 좀 길지만 천천히 봅시다.
main() 함수는 그대로이니 넘어가겠습니다.
generate_news() 함수는 원래 없었는데, 로딩 시간이 너무 오래 걸려서 추가하였습니다.

뉴스 보기 버튼을 누르면 로딩 중이라는 문구가 뜹니다

위 상태로 5분 정도 기다려야 

성공한 모습

이 결과를 볼 수 있었습니다...
하지만 이렇게 되면 프로그램 자체도 너무 무겁고 사용자 친화적이지도 않기 때문에 해당 함수를 추가하였습니다.
 
generate_news_views() 함수는 이름 그대로 생성된 뉴스를 반환하는 함수입니다.
이게 개발 기록 (2)에서는 JSON으로 반환하여 {"message": "~"} 이런 형태로 보인 거고, 지금은 html을 반환 중이기 때문에 위 사진처럼 텍스트만 온전하게 볼 수 있는 것입니다.
 
check_task_status() 함수에서 task_id를 통해 Celery 작업 결과를 반환할 수 있습니다.


 
generate_news() 함수를 사용하기 위해서는 DB를 사용해야 합니다.
main/models.py를 추가하여 아래 코드를 작성해 주세요.

from django.db import models

class FakeNews(models.Model):
    content = models.TextField()  # 생성된 뉴스 내용
    created_at = models.DateTimeField(auto_now_add=True)  # 생성 날짜

 
 
그리고 tasks.py도 수정합니다.

from celery import shared_task
from transformers import pipeline

generator = pipeline('text-generation', model='EleutherAI/gpt-neo-1.3B')

@shared_task(bind=True)
def generate_fake_news(self):
    try:
        prompt = "Generate a fake news headline about a breakthrough in technology."
        generated_news = generator(prompt, max_length=100, num_return_sequences=1, truncation=True)
        return generated_news[0]['generated_text']
    except Exception as e:
        self.update_state(state='FAILURE', meta={'exc': str(e)})
        return str(e)

로딩 문제를 개선하기 위해 pipeline()을 함수 바깥에 선언하여 한 번만 시행할 수 있도록 수정하였습니다.
이전에 max_length=100으로 설정했더니 텍스트 잘림 현상이 일어났었습니다. 이를 해결하기 위해 truncation=True를 추가하여 모델이 적절한 길이로 잘라서 처리할 수 있도록 수정하였습니다.
 
+) settings.py에 해당 코드를 추가합니다.
해당 옵션을 추가함으로써 Celery가 더 많은 상태 정보를 저장할 수 있도록 하기 위함입니다.

CELERY_RESULT_EXTENDED = True

HTML

이제 프론트 엔드 작업을 해 보겠습니다.
templates/news/generateNews.html 경로를 따라 파일을 만들고, 아래 코드를 작성해 주세요.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Generate Fake News</title>
</head>
<body>
    <h1>오늘의 뉴스</h1>
    <button id="generateButton">뉴스 보기</button>
    <div id="result"></div>
    <div id="loading" style="display:none;">로딩 중...</div>

    <script>
        document.getElementById('generateButton').addEventListener('click', function() {
            const loadingDiv = document.getElementById('loading');
            const resultDiv = document.getElementById('result');
            loadingDiv.style.display = 'block';

            // task_id를 Django 템플릿에서 전달받은 값으로 설정
            const taskId = "{{ task_id }}";
            checkTaskStatus(taskId);
        });

        function checkTaskStatus(taskId) {
            const resultDiv = document.getElementById('result');
            fetch(`/task_status/${taskId}/`)  // task_id로 상태 확인
                .then(response => response.json())
                .then(data => {
                    if (data.status === 'Completed') {
                        document.getElementById('loading').style.display = 'none';
                        resultDiv.innerHTML = '<h2>뉴스 등장</h2><p>' + data.result + '</p>';
                    } else {
                        setTimeout(() => checkTaskStatus(taskId), 1000);
                    }
                });
        }
    </script>
</body>
</html>

head 부분에 js를 연결하니 오류가 생겨서 내부에 바로 작성하였습니다.
대략적으로 페이지를 설명하자면
 
초기 화면에서 노출되는 것
- 타이틀
- 뉴스 생성 버튼
 
버튼 클릭 시 노출되는 것
- 타이틀
- 뉴스 생성 버튼
- '로딩 중...' 문구
 
로딩이 끝난 후 노출되는 것
- 타이틀
- 뉴스 생성 버튼
- 뉴스 제목
- 뉴스 내용
(로딩 중은 none 처리로 숨김)
 
버튼을 누르면 로딩 중 문구가 뜨고, 가짜 뉴스가 생성되면 로딩 중 문구는 사라지고 그 자리에 뉴스 제목과 내용을 삽입하도록 구성하였습니다.
아래 사진처럼 1초(1000)에 한 번씩 로그를 띄워 제대로 전달되는지 확인 가능하도록 하였습니다.


main/urls.py를 수정합니다.

from django.urls import path
from . import views

urlpatterns = [
    path('', views.main, name='main'), # 메인
    path('generate_news/', views.generate_news_view, name='generate_news'), # 뉴스 생성
    path('task_status/<str:task_id>/', views.check_task_status, name='check_task_status'), # 생성된 뉴스 확인
]

path를 해 줘야 해당 경로에서 페이지 확인이 가능합니다.
 
이렇게 하고 django, redis, celery를 켠 후 서버에 접속하여 해당 경로로 들어가면 결과를 확인할 수 있습니다.
로딩 시간이 아직 조금 걸리긴 하지만 이는 추후에 더 보완해 볼 예정입니다.


다음 글은 챗봇 개발로 오겠습니다!
구현은 해 놨는데 글 쓰는 게 귀찮네요  -.-;