독립 "➕ 좋아요 상위 후보" 섹션은 cardSection 에 ci 를 안 넘겨서
toggle(hlId,idx,undefined) → selCut 에 기록 안 됨 → 빌드 때 마지막 컷으로 폴백했다.
계획서는 "맨 뒤로 가니 마지막 컷 뒤에 붙는다, 배치상 문제 없다"고 적었는데 틀렸다.
_cards_by_cut 은 컷 뒤에 붙이지 않고 **컷 안에서 균등 분할**한다 —
4초짜리 마지막 컷에 5장이 들어가면 0.8초씩 번쩍인다.
AI 프롬프트가 "억지로 채우지 마라, 없으면 빈 배열로 둬라"라고 지시하므로
quota 미달이 기본 동작이고, 스펙 §7 은 그 미달을 "사람이 ➕에서 채운다"로 풀었다.
즉 이건 예외가 아니라 주 워크플로다.
- hl.cuts 가 있으면 컷마다 두 섹션(둘 다 ci=cu.i):
① 컷 N · X초 · 카드 Q장 — 자막 ② ↳ 좋아요 상위에서 채우기
②는 컷마다 반복되므로 초기 렌더를 6장(CUT_FILL_PAGE)으로 줄이고 나머지는 더보기.
- 마지막 컷 폴백 제거. ci 가 없으면 엉뚱한 컷에 넣지 말고 그 카드를 건너뛴다
(fd.append("cards") 와 sentCuts.push 는 반드시 쌍으로 — 하나만 돌면 전부 어긋난다).
- hl.cuts 가 없는 폴백 경로(⭐ + 독립 ➕)는 그대로 둔다.
같이: 컷 섹션의 '나머지'를 그 컷 시간대 언급 댓글로 좁혔다.
hl.matched 전체를 컷마다 복제하면 컷 12개 × 30장 = 360장, 하이라이트 5개면 1800장으로
부풀고(기존 300장) 같은 댓글이 여러 섹션에 중복돼 한쪽을 고르면 쌍둥이까지 하이라이트됐다.
데이터(byIdx[i].times, paste.cuts[i].start/end)는 이미 클라이언트에 다 있다.
호출되지 않는 죽은 함수 cutOf() 도 제거.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/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>
ai_pick_cuts 가 429·타임아웃·JSON 파싱 실패를 전부 {} 로 삼켜서,
build_highlight_cuts 는 여전히 타임스탬프만으로 채운 cuts 를 돌려주고
app.py 의 `if cuts:` 가 참이 되어 except 가 절대 타지 않았다.
스펙 §7이 요구한 "⚠ 추천 실패 → 기존 방식" 경고가 화면에 안 떴다.
- ai_pick_cuts: 실패는 None, 성공은 dict(빈 dict 포함).
키가 없는 경우는 실패가 아니다 — 키 없이 쓰는 것도 정상 사용이라 {} 를 준다.
실패로 치는 건 HTTP·타임아웃·응답 파싱·프롬프트 치환 오류뿐.
- 응답 파싱을 _rows_to_picks() 로 떼어내 '깨진 응답'(None)과
'어울리는 게 없어 빈 배열'({})을 구분한다. _parse_ai() 계약은 그대로 둔다.
- build_highlight_cuts → (cuts, need, ai_failed) 3-튜플.
통짜 모드는 Gemini 를 안 부르므로 ai_failed=False.
- build_cut_picks 는 ai_picks=None 이어도 안전(`ai_picks or {}`).
- app.py: ai_failed 면 warnings 에 "AI 추천 실패 — 타임스탬프만으로 배정" 한 줄.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
서버가 하이라이트마다 컷 단위 추천(hl.cuts)을 내려주기 시작해서(6~8번
커밋), 화면도 컷 소속을 알아야 카드를 그 컷 구간 안에 배치할 수 있다.
카드가 어느 컷에 속하는지(selCut)를 추적해 컷별 상한(quota)을 걸고,
sel[hlId]를 항상 컷 순서로 정렬해 업로드 파일명(001.png…)과
card_cuts 배열 인덱스가 어긋나지 않게 했다. hl.cuts가 없으면(추천
실패) 기존 ⭐ 통짜 화면으로 그대로 폴백한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
댓글 매칭 후 recommend.build_highlight_cuts()를 호출해 하이라이트별
cuts/need를 SSE result에 실어 보낸다. Gemini 호출이 블로킹이라
asyncio.to_thread로 스레드에서 돌리고, 실패해도 기존 matched/candidates로
화면이 폴백하도록 warnings에만 기록하고 드래프트 생성은 막지 않는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
컷별 댓글 추천 작업을 태스크 단위로 되돌릴 수 있게 버전관리를 시작한다.
.gitignore 로 영상·캐시(.downloads 2.7G, .comments 72M, .media 28M)와
비밀키(.gemini_key)를 제외했다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>