Commit Graph

81 Commits

Author SHA1 Message Date
02fa5be25b feat(discover): ChannelDiscoveryService(지역 Shorts 검색→떡상 채널 upsert)+설정
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:43:24 +09:00
507ae7c760 feat(discover): RecommendedChannel 엔티티+Repo, DiscoveryRanker 랭킹로직(TDD)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:41:52 +09:00
ee4d556851 docs(plan): 추천 채널 발굴 구현 계획(6 태스크)
RecommendedChannel 엔티티 → DiscoveryRanker(TDD) → ChannelDiscoveryService(검색+upsert)
→ 스케줄러 → REST → /recommend 페이지. 기존 검색/쿼터 가드 재사용.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:31:10 +09:00
9fed1e04b9 docs(spec): 추천 채널 발굴(떡상 Shorts) 설계 추가
지역(KR,JP,US) 인기 Shorts 주기 검색 → 작은구독자·고배율 떡상 채널 발굴 →
RecommendedChannel 저장 → /recommend 페이지에서 등록/제외. 쿼터 가드·기존 검색 재사용.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:07:23 +09:00
279cb4c9d0 chore(rework): 제거된 버튼의 잔여 안내문구·죽은 JS 정리
URL자막 버튼 제거에 따라 안내문/placeholder의 'URL자막' 언급 삭제,
미사용 함수 extractScript/translateToEditor 제거(copyToEditor는 유지).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 09:49:00 +09:00
0decc4439c chore(rework): 상단 버튼 정리 — URL자막·번역→재작성·[00:00]번역 제거
AI 자막(음성/화면) 기능이 번역을 대체해 중복되는 버튼 3개 삭제.
원본 다운로드·자막 추출(음성)·받은 원본 삭제·영상 업로드·에디터로 복사는 유지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 09:46:44 +09:00
d16f23a4c0 feat(rework): AI 자막 패널에 원문+번역 비교 표시
화면(Gemini)·음성 패널에서 원문과 한국어 번역을 함께 보여줘 비교 가능하게 함.
- ScriptResponseDto.Segment: original 필드 추가(NON_NULL)
- Gemini 프롬프트/스키마: original(화면 원문) + text(한국어 번역) 둘 다 요청,
  text 는 반드시 한국어(영어 방지) 명시
- rework.html: 각 줄에 한국어(위)+원문(아래 회색) 표시, 음성 패널은 Whisper 원문 사용

검증: 470 화면자막 → 원문 일본어 + 한국어 번역 + 진행형 타임스탬프 정상.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 09:12:31 +09:00
b151204267 fix(gemini): 화면 자막 타임스탬프 0/빈결과 문제 해결
원인: Gemini 2.5 Flash의 thinking(타임스탬프 추론)+JSON 출력이 자막 많은 영상에서
출력 토큰을 초과해 JSON이 잘림 → 0개 또는 타임스탬프 전부 0.

- maxOutputTokens 65536(최대)로 상향 — thinking+JSON 잘림 방지(핵심)
- temperature 0 — 일관성
- 프롬프트: 시작/끝 시각 정확히, 전부 0 금지, 누락 없이 추출하도록 보강
- 빈 결과면 최대 3회 재시도(영상이해 비결정성 보완)

검증: 470·560(자막 많은 영상) 모두 진행형 타임스탬프로 정상 추출.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 18:43:47 +09:00
99dbe80fdb fix(rework): 자막 하이라이트가 페이지 전체를 스크롤해 영상이 흔들리던 문제
재생 중 활성 세그먼트로 이동할 때 scrollIntoView가 페이지를 스크롤해 sticky 영상이
위아래로 튀던 현상 수정. 세그먼트 박스(#segmentList) 내부에서만 스크롤하도록 변경.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 18:25:41 +09:00
31b7720af9 feat(rework): '전사' → '자막 추출(음성)' 용어 변경 + 왼쪽 영상 패널 sticky
- 이해하기 쉽게 모든 '전사' 문구를 '자막 추출'로 변경(메인 버튼은 '자막 추출(음성)')
- 좌측 영상 카드 position:sticky(top:1rem) → 스크롤해도 상단에 고정되어 항상 보임

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 18:12:26 +09:00
7b94515180 fix(rework): AI 자막 - 음성 없음은 오류 아닌 안내(회색)로 표시
말소리 없는 영상(음악·화면자막만)의 음성 패널을 빨강 오류가 아니라 회색 안내
('음성 없음 — 말소리가 없거나 전사 미실행')로 표시. renderAiPanel에 note 인자 추가.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:31:02 +09:00
6380a28a6f fix(rework): Gemini 실패 사유를 사용자에게 명확히 + 음성 없음 메시지 정정
- GeminiSubtitleService: 403(프로젝트 차단)/429(한도)/400(키 무효)을 한국어 메시지로
  IllegalArgumentException throw → 'Internal Server Error' 대신 실제 사유 노출
- rework.html: 음성 패널 빈 경우 '말소리 없음(음악/화면자막만) 또는 전사 미실행'으로 정정

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:16:29 +09:00
447645c19a feat(rework): AI 자막 2종(음성 Whisper / 화면 Gemini)→한국어, 좌우 분할 + SRT
재가공 화면에 'AI 자막(음성/화면)' 카드 추가. '생성' 한 번에:
- 왼쪽(음성): 저장된 Whisper 세그먼트(정밀 타임스탬프) → LibreTranslate 한국어
- 오른쪽(화면): 유튜브 URL → Gemini가 화면 박힌 자막 추출+한국어 번역
각 패널을 [00:00] 한국어 리스트로 표시, 각각 SRT 다운로드(audio_ko/screen_ko).

- GeminiSubtitleService 신규: youtube URL을 generativelanguage API에 전송,
  responseMimeType=json 구조화 응답 파싱. buildRequest/extractSegments/sanitizeApiKey 순수+단위테스트
- sanitizeApiKey: 잘못 붙은 선행 '='·공백 정리(env var 오타 방어)
- geminiRestTemplate 빈(5분), POST /{id}/gemini-subtitles, CurationService.geminiScreenSubtitles
- 설정 gemini.api-key/model(gemini-2.5-flash), rework.html 분할 카드+JS
- 음성 한국어는 기존 translate(format=segments) 재사용

검증: 470 화면자막 → 한국어 27세그먼트 30초 추출(스타일 일본어 자막도 정확).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:04:24 +09:00
c44b16a7f4 docs(spec): AI 자막 2종(음성 Whisper / 화면 Gemini)→한국어 SRT 로 설계 갱신
좌우 분할: 왼쪽=음성(Whisper 정밀+한국어), 오른쪽=화면(Gemini 한국어). 각각 SRT 다운로드.
음성 한국어는 기존 전사+LibreTranslate 재사용, 화면은 Gemini 신규(transient).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:44:04 +09:00
aa8dc17dd8 docs(spec): Gemini URL 자막(유튜브 URL→한국어 SRT) 설계 추가
'Gemini 자막' 버튼으로 유튜브 URL을 Gemini에 보내 화면/음성 자막을 추출+한국어 번역,
[00:00] 한국어 세그먼트로 저장 → SRT 내보내기가 곧 한국어 SRT. 제거된 OCR의 대체.
무료 티어(하루 8시간) 사용, 타임스탬프는 ~1초 근사.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:34:30 +09:00
7ebee1caa3 revert(rework): 화면 자막 OCR 기능 제거
Tesseract 기반 OCR이 실제 쇼츠의 스타일 자막을 거의 못 읽어 실효성이 없고,
URL→Gemini 방향으로 전환하기로 해 h-lab의 OCR 연동을 제거한다.

- 컨트롤러 POST /{id}/ocr 제거
- CurationService.ocrScreenSubtitles 제거
- ChannelService.ocrFromCached/joinSegmentText 제거(persistScript는 전사가 계속 사용)
- rework.html '화면 자막 OCR' 버튼·crop/fps 입력·ocrScreen() 제거
- docs/python-service/ocr_video_endpoint.py(videocr 예시) 삭제

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:19:32 +09:00
fbce1ec8ea feat(rework): 화면 자막 OCR 속도(sample_fps) 컨트롤 추가, 기본 1
저사양 서버(N150)에서 sample_fps=3은 프레임 과다로 10분 타임아웃 발생.
OCR 영역(crop %) 옆에 'fps' 입력을 추가하고 기본을 1로 낮춰 빠르게 끝나게 함
(자막이 빨리 바뀌면 2~3으로 올림). ocrScreen()이 sampleFps 를 함께 전달.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:42:57 +09:00
32b2eae62c feat(rework): 받아진 상태면 '원본 다운로드' 숨기고 '전사' 버튼 노출
캐시(받은 원본) 보유 시 '원본 다운로드'를 숨기고, 그 자리에 '전사' 버튼을 띄운다
(전사만 실패했을 때 재다운로드 없이 재시도). 캐시 없으면 다시 '원본 다운로드' 표시.

- updateCacheUI: downloadBtn 숨김/표시 대칭 토글 + transcribeCachedBtn 노출
- runTranscribeCached 헬퍼 추출(다운로드 2단계 전사와 '전사' 버튼이 공유)
- '전사' 버튼 onclick=transcribeCached()

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:25:35 +09:00
bba81760a2 fix(rework): 원본 다운로드와 전사 분리 — 전사 지연/실패에 안 갇히게
기존엔 '원본 다운로드'가 다운로드+Whisper전사를 한 동기호출로 처리해, 전사 서버
(h-python)가 느리거나 타임아웃나면 화면이 '다운로드 중'에서 안 풀렸다.
다운로드(빠름)와 전사(분리)를 나눠, 다운로드는 즉시 끝나고 영상이 바로 뜨며
전사가 실패해도 다운로드 결과는 유지(화면자막 OCR/재시도 가능).

- POST /{id}/download: 다운로드만(캐시), 응답 {downloaded, sizeBytes}
- POST /{id}/transcribe-cached: 받은 원본 전사(분리)
- downloadOriginal(): 1)다운로드 즉시완료·영상표시 2)전사 별도(실패해도 다운로드 유지)

검증: /download 2.7초 완료. 전사는 분리 단계로 진행/실패 격리.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:19:18 +09:00
6a0a7294bb feat(rework): 화면 자막 OCR 에 자막영역(crop) 전달 추가
OCR 정확도·속도 향상을 위해 '자막영역 하단 N%' 입력을 추가, 플레이어 영상의 실제
해상도로 crop 픽셀(crop_x/y/width/height)을 환산해 /ocr_video 로 전달한다.
100%면 use_fullframe=true, 해상도 못 읽으면 생략(서버 기본 하단30%).

- ocrFromCached(file, formParams Map)로 일반화 — 서버 필드명 그대로 전달
- CurationService/Controller에 useFullframe·cropX/Y/Width/Height 파라미터 추가
- rework.html: 자막영역 % 입력 + ocrScreen()이 영상 해상도로 crop 계산

검증: sample_fps=3 + 하단30% crop 으로 2m28s 완료(프록시 600s 적용 후 504 해소),
crop 전달 정상 동작 확인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:53:42 +09:00
f7ac96f1dd feat(rework): 화면 자막 OCR(영상 박힌 자막) 연동
받은 원본 영상을 Python /ocr_video(ffmpeg+Tesseract)로 보내 화면에 박힌 자막을
시간 싱크 세그먼트로 추출·저장한다. 음성 전사와 같은 자리(ChannelVideoScript)에
저장되어 스크립트 리스트·SRT·번역·한국어 SRT에 그대로 흐른다.

- ChannelService.ocrFromCached + persistScript 공통 추출(전사/OCR 공유), joinSegmentText
- CurationService.ocrScreenSubtitles(받은 원본 캐시 사용), POST /{id}/ocr
- rework.html '화면 자막 OCR' 버튼 + ocrScreen()
- sampleFps/confThreshold null이면 미전송 → 서버 기본값 사용

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:31:56 +09:00
a2934af1a3 chore(translate): 번역 서비스 URL을 h-translate.tolag.shop 으로 변경
h-etc2.tolag.shop → https://h-translate.tolag.shop (application.yml 기본값 + TranslateService @Value).
새 URL 동작 확인(/translate, /languages 정상).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:05:26 +09:00
d90ee7c09a docs(ocr): videocr(PaddleOCR) 영상 자막 OCR 엔드포인트 예시 추가
영상 박힌 자막 → SRT/세그먼트(POST /ocr_video). 응답을 /transcribe 와 동일 형식
({language, segments[{start,end,text}], srt})으로 맞춰 h-lab 연동 재사용 가능하게 함.
h-python 서비스에 배포할 참고 코드.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:50:35 +09:00
e9ad800134 feat(rework): 한국어 SRT 내보내기 추가
세그먼트를 한국어로 일괄번역해 기존 타임라인(타임코드·배속·말없는구간 제거)에
그대로 끼워 SRT로 내보낸다. 원본 SRT와 별개로 '한국어 SRT' 버튼 추가.

- translateScript format=segments: 세그먼트와 1:1 정렬된 번역 배열 반환
- rework.html exportKoSrt(): texts 배열을 workingSegments에 매핑해 buildSrt로 생성
  (텍스트만 한국어로 교체, 타임코드/배속은 기존 로직 재사용)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:45:20 +09:00
198334c6f8 feat(rework): 타임라인 [00:00] 형식 번역 옵션 추가
'[00:00] 번역' 버튼 추가 — 세그먼트별로 번역해 '[mm:ss] 한국어' 줄로 재작성 칸에 내린다.
LibreTranslate의 q 배열 일괄번역(1회 호출, 입력순서 1:1 정렬)을 사용.
기존 '번역→재작성'(평문 흐름)은 그대로 유지(format=plain|timeline).

- TranslateService.translateBatch + buildBatchRequestBody/extractTranslatedTexts(+단위테스트)
- translateScript(id,target,format): timeline 분기, 세그먼트 시각 [mm:ss] 머리표시
- 컨트롤러 format 파라미터, rework.html '[00:00] 번역' 버튼

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:10:05 +09:00
77e77894c9 feat(rework): 원본 스크립트 번역 → 재작성(내 버전) 버튼 추가
자가호스팅 LibreTranslate(h-etc2.tolag.shop) 연동. 재가공 화면 '번역→재작성'
버튼으로 원본 전사 스크립트를 한국어로 번역해 재작성 칸 초안으로 채운다.
소스 언어는 전사 언어로 자동(없으면 auto), 타깃 기본 ko.

- TranslateService 신규: POST /translate {q,source,target} → translatedText
  (요청 조립/응답 파싱 순수 메서드 + 단위테스트)
- POST /{id}/translate 엔드포인트, CurationService.translateScript
- rework.html: '번역→재작성' 버튼 + translateToEditor()
- 설정 translate.base-url(기본값 有)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:41:23 +09:00
ff9609c9d1 feat(rework): 받은 원본을 왼쪽 플레이어에 싣고 타임라인 클릭으로 seek
원본 다운로드/캐시 보유 시 왼쪽 플레이어를 YouTube 임베드 대신 받은 원본 mp4로
전환한다. 세그먼트(타임라인) 클릭 시 기존 seekTo/하이라이트가 그대로 동작해
해당 지점으로 이동·재생된다. (업로드본이 있으면 업로드본 우선)

- GET /{id}/download/file: 받은 원본 스트리밍(Resource 반환 → HTTP Range 206 자동 지원)
- CurationService.cachedDownloadFile(id)
- rework.html: showServerVideoInPlayer/showYoutubeInPlayer, updateCacheUI가 플레이어 전환,
  다운로드 직후/진입 시 캐시 있으면 자동 적용, 삭제 시 YouTube로 복귀

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:58:23 +09:00
986dcffd55 feat(rework): 받은 원본 삭제 + 영상 삭제 시 캐시 자동정리
1) 재가공 화면에 '받은 원본 삭제' 버튼 추가(캐시 보유 시에만 노출).
   GET/DELETE /{id}/download 로 캐시 상태 조회·삭제(자막/세그먼트는 유지).
   진입 시 캐시 상태를 조회해 삭제버튼 노출 + 렌더 캐시 사용 여부를 갱신.
2) 수집함 영상 삭제 시 downloads/{id}.mp4 도 함께 삭제(고아 파일 방지).

- VideoDownloadService.deleteCache(id) + 단위테스트(@TempDir)
- CurationService: downloadStatus/deleteDownloadCache, delete() 캐시 정리

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:10:02 +09:00
f4ac1be171 docs(rework): 사용법 모달에 '원본 다운로드' 이미지 가이드 추가
재가공 사용법 모달 상단에 실제 화면 캡쳐 3장으로 단계별 안내 추가:
1) 원본 다운로드 버튼  2) 자동 전사된 시간싱크 자막  3) 내보내기(업로드 불필요).
?help=1 딥링크로 사용법을 바로 펼칠 수 있게 함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:09:30 +09:00
26b8be1ff1 feat(rework): yt-dlp 원본 다운로드 → 전사·렌더 파이프라인 자동연결
재가공 화면 '원본 다운로드' 버튼으로 저장된 videoId를 yt-dlp(로컬 ProcessBuilder)로
받아 서버에 캐시하고, 그 파일을 기존 Python /transcribe·/render 에 그대로 투입한다.
수동 다운로드+업로드 단계를 전사·렌더 양쪽에서 제거.

- VideoDownloadService 신규: 인자 리스트 ProcessBuilder(셸 미사용), videoId 검증, 캐시 조회
- ChannelService: 전사·렌더를 Resource 기반 공통 메서드로 추출(업로드/캐시 공유)
- 컨트롤러: POST /{id}/download(다운로드+전사), /render 의 file 을 선택값으로(없으면 캐시)
- rework.html: '원본 다운로드' 버튼 + downloadOriginal(), 렌더가 서버 캐시 재사용
- 설정 ytdlp.*/download.dir(기본값 有), downloads/ gitignore
- buildCommand/videoId 검증 단위테스트(src/test 신규)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:58:50 +09:00
a177b0c987 docs(spec): yt-dlp 다운로드 → 재가공 파이프라인 자동연결 설계 추가
재가공 화면 '원본 다운로드' 버튼으로 yt-dlp 로컬 실행 → 서버 캐시 →
기존 Python 전사/렌더 파이프라인 재사용. 전사·렌더 양쪽에서 수동 업로드 제거.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:29:05 +09:00
8ec6268a1e fix(ui): 모바일에서 사이드바 펼치면 텍스트 라벨도 표시
모바일(≤768px)에서 사이드바를 펼쳐도 아이콘만 보이던 문제 수정.
태블릿용(≤1024px) 규칙이 .nav-text/.brand-text/.nav-section/.user-info를
display:none 처리하는데, 모바일 오버레이로 펼친 상태(.sidebar:not(.collapsed))
에서 그게 안 풀려 아이콘만 남았다.
→ 펼친 상태에서 라벨 다시 표시(display:revert) + nav 좌측 정렬. 모바일 사이드바를
전체폭 대신 280px 드로어 오버레이(그림자)로 정리.
CSS 변경에 맞춰 캐시버스팅 버전 20260617b 로 bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:26:42 +09:00
921c24f143 fix(ui): 모바일 깨짐 수정 — 표 카드뷰 opt-in, 대시보드 반응형, CSS 캐시버스팅
폰에서 화면이 깨지던 문제 수정.
- 전역 반응형 표 '카드뷰'가 모든 table에 적용돼, data-label 없는 목록 표
  (수집함·발굴·발행 등)가 거대한 행으로 변해 페이지가 32만px로 늘어남.
  → 카드뷰를 .rcards opt-in 으로 스코프. 그 클래스 가진 레거시 표
  (multi_channel_videos·videos)만 카드뷰, 나머지는 모바일에서 가로 스크롤.
- 대시보드: 모바일(≤680px) 미디어쿼리 추가 — 마스트헤드 세로 정렬, 긴
  브레드크럼 숨김, 떡상 리더보드 액션 버튼을 아랫줄 우측 정렬, 숫자 축소.
- base.html: CSS/JS 링크에 v= 캐시버스팅 — 배포 후 브라우저가 옛 CSS를
  캐시해 스타일이 깨지는 것 방지(CSS 변경 시 버전 올리기).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:56:50 +09:00
e0d92792b1 feat(ui): 전체 화면을 편집(editorial) 디자인으로 통일
대시보드의 editorial 정체성을 공용 디자인 토큰으로 끌어올려 전 페이지에 전파.
- variables.css: 종이 캔버스 + 잉크 텍스트 + 버밀리언(#ff4d23) 단색 액센트,
  Bricolage Grotesque(헤딩, 라틴) + Hanken Grotesk(본문) + JetBrains Mono,
  샤프한 모서리(radius 축소). 라이트 기본 + 다크. 기존 변수 별칭 유지로
  각 페이지가 자동 적응.
- style.css: 헤딩 디스플레이 페이스, primary 버튼 단색 버밀리언, 배지 각짐 등
  편집풍 마무리 터치.
한글은 디스플레이/모노 폰트에 없으므로 시스템 폰트로 자연 폴백.
수집함·칸반 라이트/다크 Playwright 검증.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:15:59 +09:00
c0ade287c2 feat(ui): 썸네일 클릭 미리보기를 칸반·채널상세·프로덕션상세에 추가
수집함·발굴·대시보드처럼, 썸네일 있는 나머지 화면에서도 썸네일을 클릭하면
YouTube 9:16 미리보기 팝업이 뜨도록 통일.
- 칸반(board): 카드 썸네일 클릭(드래그와 분리 위해 stopPropagation)
- 채널상세(channel_detail): 영상 목록 썸네일(제목 링크는 YouTube 유지)
- 프로덕션상세(production_detail): videoUrl에서 11자리 id 추출해 임베드,
  id 없으면 새 탭 폴백
각 페이지에 동일 모달(배경/ESC 닫힘, 닫을 때 iframe 비워 재생 중지) 추가.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:18:09 +09:00
c7ce8de90e feat(ui): 대시보드 떡상 리더보드 썸네일 클릭 미리보기
수집함처럼, 떡상 후보 리더보드의 썸네일을 클릭하면 YouTube 임베드(9:16)
미리보기 팝업이 바로 뜨도록 추가. 편집 정체성에 맞춘 모달(.fd-vmodal),
배경 클릭/ESC로 닫힘, 닫을 때 iframe 비워 재생 중지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:52:24 +09:00
b97e4644ea fix(ui): 대시보드 빈 화면 수정 — Thymeleaf [[ 충돌 제거
대시보드 인라인 스크립트의 `const stages = [['미검토',...],[...]]` 에서
앞쪽 `[[` 가 Thymeleaf 인라인 표현식 문법(`[[${...}]]`)과 충돌해,
렌더링이 그 지점에서 예외로 잘려 응답이 truncate → JS 미완성 → 빈 화면이었다.
(로그의 "response committed already" 예외가 이 증상)

배열 리터럴을 줄바꿈/공백으로 풀어 `[[`·`]]` 인접을 제거. 렌더 결과가
온전해짐(잘림 21217B → 정상 24519B, </html>·loadDashboard() 복구).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:38:28 +09:00
a36783f81f chore: 원격 푸시 재테스트
푸시가 반복적으로 정상 동작하는지 다시 확인.
docs/push-test.md 에 재테스트 줄 추가.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:22:14 +09:00
97192d7372 chore: 원격 푸시 동작 테스트
Gitea 원격(origin) 푸시가 정상 동작하는지 확인하는 테스트 커밋.
docs/push-test.md 추가 — 확인 후 삭제 가능.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:07:01 +09:00
22dd2f6d87 feat(ui): action-first editorial dashboard (frontend-design)
Rebuild the dashboard with a distinctive editorial identity (Bricolage Grotesque
display + JetBrains Mono tickers + paper canvas + vermilion signal accent, ruled
layout, grain, staggered load) scoped to .fd-dash so other pages keep the SaaS
theme. UX is action-first:
- action bar: 미검토/떡상 후보/재가공 대기 with primary CTAs (real counts)
- 떡상 후보 leaderboard wired to real /api/dashboard/summary outperformers, with
  working per-row 재가공(/rework/{id}) and 제외(status EXCLUDED) actions
- pipeline funnel with bottleneck callout + 칸반 CTA
- source/format + publish with empty-state guidance
Light/dark aware. Editorial fonts loaded in base.html.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:07:01 +09:00
4508c3ef18 Add Jenkinsfile: build/test/deploy pipeline 2026-06-16 12:22:57 +09:00
cc3904c307 docs: rewrite README with badges and accurate stack
Replace the outdated React/Vite/8080/H2 README with an accurate, shielded
overview: Spring Boot 3.4 / Java 21 Thymeleaf SSR, the collection→discovery→
curation→rework→publish pipeline, subtitle studio (Whisper+ffmpeg), light/dark
UI, getting-started, env vars, and project structure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 10:36:44 +09:00
dd42499f43 feat(ui): localize production page + page-header
Production -> 프로덕션, Crawl History -> 크롤 이력, table headers to Korean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:33:45 +09:00
652d25a483 feat(ui): discover table fits viewport (merged 영상 column)
Merge thumbnail+title+channel into one "영상" column like 수집함 so the table
fits without horizontal scroll. Promote .coll-table/.row-sel to global style.css
for reuse across list pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:32:27 +09:00
f3be6bd803 feat(ui): localize channels page to Korean
채널/추적 안내/전체 선택/채널 추가/구독자/영상 수/총 조회수/상세/방문/삭제.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:24:49 +09:00
1ebe2dda44 feat(ui): discover filter chips + help modals, localize, tokenize long pages
- discover: filter converted to toggle chips + inline selects (matches 수집함);
  page-header + 사용법 modal (배율 지표/필터/고른 뒤 안내)
- rework: 사용법 modal (전사·세그먼트·무음제거·내보내기·발행 guide) + page-header
- dashboard: h1 "Dashboard" -> "대시보드"
- channel_detail: sort-active header uses accent color (was invisible text-white)
- multi_channel_videos/videos/production_detail: tokenize dark-assuming colors
  and title-link text-white for light-theme readability

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:22:55 +09:00
52c6b51e61 feat(ui): roll out light/dark theme to board, publish, rework, discover, etc.
- board: tokenized kanban columns/cards; page-header + 사용법 modal (drag guide)
- publish: tokenized table/tabs; accent tab-active; page-header + 사용법 modal
- rework: tokenized the many dark-assuming inline styles so the editor renders
  correctly in the light theme
- discover/channels/production/channel_detail: tokenized dark-assuming colors
  (white text, translucent-white surfaces, #1e1e2d) for light-theme readability

Verified board/publish/rework/discover render cleanly in light.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:07:51 +09:00
616003479b feat(ui): collection — compact category, help modal, fit-to-width table
- Category management collapsed into a slim single-row strip
- "사용법" button opens a reusable help modal (.modal-overlay/.modal-card +
  .help-item) explaining categories, filters, list view, and row actions
- Merge thumbnail+title+channel into one "영상" column and tighten cells so the
  whole table fits the viewport — no horizontal scroll on page or table card
- #mainContent min-width:0 to avoid flex overflow

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 23:12:03 +09:00
fc58a32a20 feat(ui): collection filter toolbar — toggle chips + tidy inline selects
Replace raw checkboxes + stacked-label dropdowns with pill toggle chips
(.chip, accent when checked via :has) and a single aligned toolbar row;
result count as a badge. Adds reusable .toolbar/.chip/.field components.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 22:45:05 +09:00
7497cb08ce fix(ui): collection page readable in light theme
Tokenized dark-assuming inline colors (white title/dropdown text, translucent
white surfaces, #1e1e2d modal) so the collection table renders correctly in the
light theme; header uses shared page-header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 22:39:45 +09:00