내 영상에 달린 댓글 중 내가 답 안 한 것만 모아, 영상 내용까지 근거로
답글 초안을 만든다. 조회는 나오는데 구독이 안 붙는 문제(59영상 36만 조회
구독 38)에 직접 닿는 지점이라 여기부터 손댔다.
- 미응답 판정은 totalReplyCount 가 아니라 답글 작성자에 내 채널이 있는지로 본다.
남이 대댓글만 단 댓글은 여전히 내가 답해야 하기 때문이다.
- 수집 중 내가 단 답글을 발견하면 말투 샘플로 모아둔다. 실측 17개가 학습됐고
초안이 그 톤을 따른다. 8자 미만("ㅋㅋㅋ")은 말투 정보가 없어 제외.
- 영상 맥락은 Gemini 에 유튜브 URL 을 직접 준다. 내 영상은 30초 쇼츠라 토큰이
적고 화면에 박힌 자막까지 읽힌다. 영상당 1회만 만들어 캐시한다.
- 실제 게시는 OAuth 가 없어 불가 — 초안 복사까지가 끝이다.
실측에서 잡은 결함 두 개를 고쳤다.
1) 요약 생성 때 제목을 안 넘겨서 Gemini 가 출연자 이름을 추측했다. "이채영"
영상을 "이채연"으로 요약했고 그 오류가 답글까지 전파됐다. 제목을 함께 주고
"제목 표기를 글자 그대로" 규칙을 넣어 해결. 캐시가 오염된 경우를 위해
refreshContext 옵션을 두고, '다시 생성'은 요약도 다시 읽게 했다.
2) 영상 요약이 없는데도 규칙에 "[영상 내용]을 근거로"가 남아 없는 섹션을
가리켰다. 요약 유무에 따라 규칙을 갈랐다(테스트가 잡음).
쿼터: 영상당 1 unit — 59개 수집에 59 units.
테스트 11건 추가(미응답 판정·말투 샘플·프롬프트 조립·응답 파싱) — 총 106건 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
64 lines
2.7 KiB
Java
64 lines
2.7 KiB
Java
package com.hlab.yanalyst.domain.reply;
|
|
|
|
import com.hlab.yanalyst.domain.reply.dto.ReplyCommentDto;
|
|
import com.hlab.yanalyst.global.common.ApiResponse;
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
/** 내 채널 댓글 답장 초안 API. 실제 게시는 OAuth 가 없어 불가 — 초안까지만. */
|
|
@RestController
|
|
@RequestMapping("/api/replies")
|
|
@RequiredArgsConstructor
|
|
@Tag(name = "Comment Reply API", description = "내 채널 미응답 댓글 수집 + 답글 초안 생성")
|
|
public class CommentReplyController {
|
|
|
|
private final CommentReplyService service;
|
|
|
|
@GetMapping
|
|
@Operation(summary = "답장 대기 목록", description = "status 미지정이면 완료 제외 전체. 좋아요 많은 순.")
|
|
public ApiResponse<List<ReplyCommentDto>> queue(@RequestParam(required = false) String status) {
|
|
return ApiResponse.ok(service.queue(status));
|
|
}
|
|
|
|
@GetMapping("/stats")
|
|
@Operation(summary = "현황", description = "미응답/초안있음/완료 건수와 학습된 말투 샘플 수")
|
|
public ApiResponse<Map<String, Object>> stats() {
|
|
return ApiResponse.ok(service.stats());
|
|
}
|
|
|
|
@PostMapping("/collect")
|
|
@Operation(summary = "댓글 수집",
|
|
description = "내 채널 영상 전체를 돌며 내가 답글 안 단 댓글을 모은다. 영상당 1 unit.")
|
|
public ApiResponse<Map<String, Object>> collect() {
|
|
return ApiResponse.ok(service.collectAll());
|
|
}
|
|
|
|
@PostMapping("/{id}/draft")
|
|
@Operation(summary = "초안 생성",
|
|
description = "영상 내용 + 댓글을 근거로 서로 다른 초안 2개 생성. "
|
|
+ "refreshContext=true 면 캐시된 영상 요약을 버리고 다시 만든다(요약이 틀렸을 때).")
|
|
public ApiResponse<ReplyCommentDto> draft(@PathVariable Long id,
|
|
@RequestParam(defaultValue = "false") boolean refreshContext) {
|
|
return ApiResponse.ok(service.generateDrafts(id, refreshContext));
|
|
}
|
|
|
|
@PostMapping("/{id}/done")
|
|
@Operation(summary = "완료 표시", description = "실제로 답글을 달았을 때 목록에서 내린다")
|
|
public ApiResponse<Void> done(@PathVariable Long id) {
|
|
service.markDone(id);
|
|
return ApiResponse.ok(null);
|
|
}
|
|
|
|
@PostMapping("/{id}/undo")
|
|
@Operation(summary = "완료 취소", description = "다시 미응답으로 되돌린다")
|
|
public ApiResponse<Void> undo(@PathVariable Long id) {
|
|
service.markPending(id);
|
|
return ApiResponse.ok(null);
|
|
}
|
|
}
|