추천 단계에 진행 표시 추가 (최대 450초 무반응 제거)
/auto/stream 은 하이라이트 5개를 순차로 돌며 각각 Gemini 를 부른다(timeout=90). manifest 에 recommend 스텝이 없고 루프 안에 로그도 없어서, 마지막으로 보이는 게 "댓글 수집 done" 이고 그 뒤 최악 450초 동안 아무 이벤트도 안 나갔다. 사용자가 멈춘 줄 알고 새로고침하면 분석이 통째로 날아간다. 순차 호출 자체는 유지한다 — 동시에 5발을 쏘면 429 가 난다(Step 3 도 같은 이유로 시차 재시도를 쓴다). 대신 보이게만 만든다: - 세 갈래 manifest(paste / wpaste / else) 전부에 recommend 스텝 추가 - 루프 앞뒤로 step start/done(detail = 처리한 하이라이트 수) - 하이라이트마다 "ID n 컷별 댓글 추천 중… (i/N, 컷 M개)" 로그 한 줄 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1e43530933
commit
4e479b50ce
@ -12,6 +12,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
@ -354,6 +355,7 @@ async def auto_stream(aid: str) -> StreamingResponse:
|
|||||||
yield _sse({"type": "manifest", "steps": [
|
yield _sse({"type": "manifest", "steps": [
|
||||||
{"id": "parse", "label": "오팔 JSON 파싱"},
|
{"id": "parse", "label": "오팔 JSON 파싱"},
|
||||||
{"id": "comments", "label": "댓글 수집 (h-lab)"},
|
{"id": "comments", "label": "댓글 수집 (h-lab)"},
|
||||||
|
{"id": "recommend", "label": "컷별 댓글 추천"},
|
||||||
]})
|
]})
|
||||||
yield _sse({"type": "step", "id": "parse", "status": "start"})
|
yield _sse({"type": "step", "id": "parse", "status": "start"})
|
||||||
import re as _re
|
import re as _re
|
||||||
@ -427,6 +429,7 @@ async def auto_stream(aid: str) -> StreamingResponse:
|
|||||||
yield _sse({"type": "manifest", "steps": [
|
yield _sse({"type": "manifest", "steps": [
|
||||||
{"id": "parse", "label": "구간 JSON 파싱"},
|
{"id": "parse", "label": "구간 JSON 파싱"},
|
||||||
{"id": "comments", "label": "댓글 수집 (h-lab)"},
|
{"id": "comments", "label": "댓글 수집 (h-lab)"},
|
||||||
|
{"id": "recommend", "label": "컷별 댓글 추천"},
|
||||||
]})
|
]})
|
||||||
yield _sse({"type": "step", "id": "parse", "status": "start"})
|
yield _sse({"type": "step", "id": "parse", "status": "start"})
|
||||||
try:
|
try:
|
||||||
@ -444,6 +447,7 @@ async def auto_stream(aid: str) -> StreamingResponse:
|
|||||||
if mode == "full":
|
if mode == "full":
|
||||||
steps.append({"id": "step3", "label": "편집안 생성 (Gemini, 구간별 동시)"})
|
steps.append({"id": "step3", "label": "편집안 생성 (Gemini, 구간별 동시)"})
|
||||||
steps.append({"id": "comments", "label": "댓글 수집 (h-lab)"})
|
steps.append({"id": "comments", "label": "댓글 수집 (h-lab)"})
|
||||||
|
steps.append({"id": "recommend", "label": "컷별 댓글 추천"})
|
||||||
yield _sse({"type": "manifest", "steps": steps})
|
yield _sse({"type": "manifest", "steps": steps})
|
||||||
# 댓글은 URL을 이미 아니까 Step1 과 동시에 수집
|
# 댓글은 URL을 이미 아니까 Step1 과 동시에 수집
|
||||||
com_task = asyncio.create_task(asyncio.to_thread(hlab.fetch_comments, url))
|
com_task = asyncio.create_task(asyncio.to_thread(hlab.fetch_comments, url))
|
||||||
@ -526,13 +530,21 @@ async def auto_stream(aid: str) -> StreamingResponse:
|
|||||||
# 후보(candidates)는 분:초 언급이 아예 없는 댓글만 — 타임스탬프 댓글은
|
# 후보(candidates)는 분:초 언급이 아예 없는 댓글만 — 타임스탬프 댓글은
|
||||||
# 자기 구간의 ⭐에서 잡히므로, 다른 구간 얘기하는 댓글이 섞이지 않게.
|
# 자기 구간의 ⭐에서 잡히므로, 다른 구간 얘기하는 댓글이 섞이지 않게.
|
||||||
#
|
#
|
||||||
|
# 하이라이트마다 Gemini 를 순차로 부른다(동시 호출은 429 를 부른다 — Step 3 도 같은 이유로
|
||||||
|
# 시차 재시도를 쓴다). 최악 5×90초라 진행 표시가 없으면 사용자가 멈춘 줄 알고 새로고침해
|
||||||
|
# 분석이 통째로 날아간다 → 스텝 + 하이라이트별 로그를 반드시 흘린다.
|
||||||
no_ts = [c for c in comments if not c["times"]]
|
no_ts = [c for c in comments if not c["times"]]
|
||||||
for h in highlights:
|
targets = [h for h in highlights if "paste" in h]
|
||||||
if "paste" not in h:
|
yield _sse({"type": "step", "id": "recommend", "status": "start"})
|
||||||
continue
|
t_rec = time.perf_counter()
|
||||||
|
for n, h in enumerate(targets, 1):
|
||||||
matched = hlab.match_window(comments, h["start"], h["end"])
|
matched = hlab.match_window(comments, h["start"], h["end"])
|
||||||
h["matched"] = matched
|
h["matched"] = matched
|
||||||
h["candidates"] = hlab.top_liked(no_ts, set(matched), len(no_ts))
|
h["candidates"] = hlab.top_liked(no_ts, set(matched), len(no_ts))
|
||||||
|
yield _sse({"type": "log",
|
||||||
|
"msg": f"ID {h.get('id')} 컷별 댓글 추천 중… "
|
||||||
|
f"({n}/{len(targets)}, 컷 "
|
||||||
|
f"{len(h['paste'].get('cuts') or [])}개)"})
|
||||||
# 컷별 추천 — 실패해도 위의 matched/candidates 로 화면이 돌아간다.
|
# 컷별 추천 — 실패해도 위의 matched/candidates 로 화면이 돌아간다.
|
||||||
# Gemini 호출이 섞여 있어 블로킹이므로 스레드로 뺀다.
|
# Gemini 호출이 섞여 있어 블로킹이므로 스레드로 뺀다.
|
||||||
try:
|
try:
|
||||||
@ -549,6 +561,9 @@ async def auto_stream(aid: str) -> StreamingResponse:
|
|||||||
warnings.append(
|
warnings.append(
|
||||||
f"ID {h.get('id')} 컷별 추천 실패 — 기존 방식으로 표시 "
|
f"ID {h.get('id')} 컷별 추천 실패 — 기존 방식으로 표시 "
|
||||||
f"({type(exc).__name__}: {exc})")
|
f"({type(exc).__name__}: {exc})")
|
||||||
|
yield _sse({"type": "step", "id": "recommend", "status": "done",
|
||||||
|
"elapsed": round(time.perf_counter() - t_rec, 1),
|
||||||
|
"detail": f"{len(targets)}개 하이라이트"})
|
||||||
yield _sse({"type": "result", "highlights": highlights,
|
yield _sse({"type": "result", "highlights": highlights,
|
||||||
"comments": comments, "warnings": warnings})
|
"comments": comments, "warnings": warnings})
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user