회원가입

[개발] [요약 내용 조회하기] ChatGPT 로 블로그 글 요약하기

Beany 2024-09-21

ChatGPT 요약

이 블로그 포스트는 요약된 정보를 조회하기 위해 디테일 페이지에서 요약 정보를 함께 가져오는 방법에 대한 내용을 다루고 있습니다. Django를 활용하여 게시글의 요약 정보를 조회하고, 요약 상태에 따라 다른 내용을 보여주는 기능을 구현하는 방법이 소개되었습니다. 또한 요약 정보가 "PROCESSING" 상태인 경우 폴링을 통해 주기적으로 요약 완료 여부를 확인하는 방법도 다루고 있습니다. 본문에는 관련된 코드 예시와 요약 정보의 상태에 따른 다른 화면 출력 예시가 포함되어 있습니다.

이제 요약된 정보를 조회를 해보자.

요약된 정보를 조회하기 위해서는 게시글 디테일 페이지에 접근할 때, 그 내용까지 같이 전달이 필요하다.

디테일페이지에 요약 정보도 같이 조회해서 가져오자.

 

 

요약본 조회하기


chatgpt.services.py

def get_latest_post_summary_by_post_id(post_id: int) -> PostSummary:
    return PostSummary.objects.filter(post_id=post_id).order_by('-created_at').first()

 

board.views.py

post_summary 를 조회한다.

def post_detail(request, board_url, pk):
    qs = Post.objects.active().filter(
        board__url=board_url
    ).select_related(
        'board'
    ).order_by(
        '-id'
    )

    prev_post = qs.filter(id__lt=pk).first()
    next_post = qs.filter(id__gt=pk).order_by('id').first()

    post = get_object_or_404(qs, board__url=board_url, pk=pk)
    post_summary = get_latest_post_summary_by_post_id(post.id)

    if request.user.is_authenticated:
        like_check = Like.objects.filter(author=request.user, post=post).exists()
    else:
        like_check = False

    context = {
        'like_check': like_check,
        'qs': qs,
        'post': post,
        'post_summary': post_summary,
        'prev_post': prev_post,
        'next_post': next_post,
    }

    return render(request, 'board/post.html', context)

 

 

board/post.html

<style>
        p {
            word-break: break-word;
        }

        .other_post {
            text-decoration: auto;
        }

        .reply_call:hover {
            cursor: pointer;
        }

        .reply_del:hover {
            cursor: pointer;
        }

        .rereply_del:hover {
            cursor: pointer;
        }

        .new-list-title {
            font-size: small;
        }

        .progress-container {
            left: 0;
            width: 100%;
            height: 0.4em;
            margin-bottom: 0px;
            position: fixed;
            top: 0px;
            overflow: hidden;
            background-color: white;
            content: "";
            display: table;
            table-layout: fixed;
            z-index: 1000;
        }

        .progress-bar {
            width: 0%;
            float: left;
            height: 100%;
            z-index: 99;
            max-width: 100%;
            background: rgb(132, 94, 194);
            background: linear-gradient(90deg, rgba(132, 94, 194, 1) 5%, rgba(44, 115, 210, 1) 22%, rgba(0, 129, 207, 1) 42%, rgba(0, 137, 186, 1) 62%, rgba(0, 142, 155, 1) 85%, rgba(0, 143, 122, 1) 100%);
            -webkit-transition: width .4s ease;
            -o-transition: width .4s ease;
            transition: width .4s ease;
        }

        .summary-card {
            width: 100%;
            max-width: 1200px;
            margin: 20px 0 20px 0;
            padding: 20px;
            border: 1px solid #D2D2D2;
            border-radius: 8px;
            box-sizing: border-box;
        }

        .summary-card-header {
            display: flex;
            align-items: center;
            margin-bottom: 20px;
        }

        .summary-card-header img {
            width: 40px;
            height: 40px;
            margin-right: 10px;
        }

        .summary-card-header h3 {
            margin: 0;
            font-size: 1.2em;
        }

        .summary-card-content {
            font-size: 1em;
            color: #333;
            line-height: 1.6;
        }
        
        .loading-spinner {
        border: 4px solid rgba(0, 0, 0, 0.1);
        border-top: 4px solid #3498db; /* 파란색 */
        border-radius: 50%;
        width: 20px;
        height: 20px;
        animation: spin 1s linear infinite;
        display: inline-block;
        margin-right: 10px; /* 텍스트와 스피너 간격 */
        }

        @keyframes spin {
            0% {
                transform: rotate(0deg);
            }
            100% {
                transform: rotate(360deg);
            }
        }
</style>


{% if post_summary %}
    <div class="summary-card">
        <div class="summary-card-header">
            <h3>ChatGPT 요약</h3>
        </div>
        {% if post_summary.status == 'DONE' %}
        <div class="summary-card-content" id="summary-card-content-done">
            {{ post_summary.body }}
        </div>
        {% elif post_summary.status == 'PROCESSING' %}
        <div class="summary-card-content" id="summary-card-content-processing">
            <div class="loading-spinner"></div>
            요약중입니다. 잠시만 기다려주세요.
        </div>
        {% elif post_summary.status == 'FAIL' %}
        <div class="summary-card-content" id="summary-card-content-fail">
            요약에 실패했습니다... :(
        </div>
        {% endif %}
    </div>
{% endif %}

간단하게 이렇게 작성했다.

 

 

폴링하기


우선 신규 API 를 생성합니다.

chatgpt.views.py

from django.http import JsonResponse
from chatgpt.services import get_latest_post_summary_by_post_id


def get_summary_by_post_id(request, post_id: int):
    if post_id is None:
        return JsonResponse({'error': 'post_id is required'}, status=400)

    post_summary = get_latest_post_summary_by_post_id(post_id)
    context = {
        'status': post_summary.status if post_summary else None,
        'summary': post_summary.body if post_summary else None,
    }
    return JsonResponse(context, status=200)

 

PROCESSING 인 부분에 추가 내용을 적을 것입니다. 여기서는 계속 polling 하는 javascript 코드를 작성할 것입니다.

{% if post_summary %}
        <script>
            document.addEventListener('DOMContentLoaded', function () {
                // "summary-card-content-processing" 요소가 있는지 확인
                let processingElement = document.getElementById('summary-card-content-processing');

                // 요소가 존재할 경우에만 폴링 시작
                if (processingElement) {
                    let pollingInterval = setInterval(function () {
                        // API를 호출 (여기선 fetch를 예로 사용)
                        fetch("{% url 'chatgpt:get_summary_by_post_id' post_id=post.id %}")  // API 엔드포인트로 변경
                            .then(response => response.json())
                            .then(data => {
                                if (data.status !== 'PROCESSING') {
                                    let div_id = 'summary-card-content-' + data.status.toLowerCase()
                                    document.getElementById('summary-card-content-processing').id = div_id;
                                    document.getElementById(div_id).innerHTML = data.summary || '';
                                    clearInterval(pollingInterval);
                                }
                            })
                            .catch(error => {
                                console.error('Error fetching the API:', error);
                                // 에러 발생 시에도 폴링 중단할 수 있음
                                clearInterval(pollingInterval);
                            });
                    }, 5000);  // 5000ms = 5초
                }
            });
        </script>
{% endif %}

 

 

완성


요약 로딩

 

요약 존재

 

요약 실패

0 0
블로그 일기
제 블로그의 고도화 과정을 설명합니다. 이는 코드 리팩토링과 추가된 기능들에 대해 기록하기 위한 게시판입니다. 어떤 기능이 추가되었는지, 무엇이 개선되었는지 등 고도화되는 과정을 자세히 다룰 예정입니다.
Yesterday: 456
Today: 101