package com.hlab.yanalyst.service; import com.hlab.yanalyst.domain.opal.*; import com.hlab.yanalyst.domain.script.ScriptGen; import com.hlab.yanalyst.domain.script.ScriptGenRepository; import com.hlab.yanalyst.domain.video.YtVideo; import com.hlab.yanalyst.domain.video.YtVideoRepository; import com.hlab.yanalyst.service.external.ExternalApiService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @Service @RequiredArgsConstructor @Transactional public class AnalysisWorkflowService { private final YtVideoRepository ytVideoRepository; private final ScriptGenRepository scriptGenRepository; private final OpalDraftRepository opalDraftRepository; private final OpalFinalRepository opalFinalRepository; private final OpalFinalAssetRepository opalFinalAssetRepository; private final ExternalApiService externalApiService; private final ObjectMapper objectMapper; public ScriptGen generateScript(Long videoId) { YtVideo video = ytVideoRepository.findById(videoId) .orElseThrow(() -> new IllegalArgumentException("Video not found")); String scriptContent = externalApiService.generateScript(video.getVideoUrl()); ScriptGen scriptGen = scriptGenRepository.findById(videoId).orElse(new ScriptGen()); if (scriptGen.getVideoId() == null) { scriptGen.setVideo(video); } scriptGen.setResponseText(scriptContent); scriptGen.setStatus("SUCCESS"); // Update video status video.setStatus("SCRIPT_READY"); return scriptGenRepository.save(scriptGen); } public OpalDraft generateDraft(Long videoId, String feedback, String mode) { YtVideo video = ytVideoRepository.findById(videoId) .orElseThrow(() -> new IllegalArgumentException("Video not found")); ScriptGen scriptGen = scriptGenRepository.findById(videoId) .orElseThrow(() -> new IllegalArgumentException("Script not found")); var draftDto = externalApiService.generateOpalDraft(scriptGen.getResponseText(), feedback, mode); OpalDraft draft = new OpalDraft(); draft.setVideo(video); draft.setOldScriptSummary(draftDto.getOldScriptSummary()); draft.setNewScriptSummary(draftDto.getNewScriptSummary()); // For compatibility, we can concatenate or just leave responseText empty/null, or use New Summary as main response draft.setResponseText(draftDto.getNewScriptSummary()); draft.setUserFeedback(feedback); Integer maxVersion = opalDraftRepository.findByVideo_VideoIdOrderByVersionNoDesc(videoId) .stream().findFirst().map(OpalDraft::getVersionNo).orElse(0); draft.setVersionNo(maxVersion + 1); draft.setStatus("SUCCESS"); video.setStatus("DRAFTING"); return opalDraftRepository.save(draft); } public OpalFinal acceptDraft(Long videoId, Long draftId) { YtVideo video = ytVideoRepository.findById(videoId).orElseThrow(); OpalDraft draft = opalDraftRepository.findById(draftId).orElseThrow(); if (!draft.getVideo().getVideoId().equals(videoId)) { throw new IllegalArgumentException("Draft does not match video"); } // Deactivate existing active final opalFinalRepository.findByVideo_VideoIdAndIsActiveTrue(videoId) .ifPresent(existing -> { existing.setIsActive(false); opalFinalRepository.save(existing); }); // Mark draft accepted draft.setIsAccepted(true); draft.setAcceptedAt(LocalDateTime.now()); opalDraftRepository.save(draft); OpalFinal opalFinal = new OpalFinal(); opalFinal.setVideo(video); opalFinal.setDraft(draft); opalFinal.setFinalScriptText(draft.getResponseText()); opalFinal.setIsActive(true); return opalFinalRepository.save(opalFinal); } public OpalFinalAsset generateFinalAsset(Long videoId) { YtVideo video = ytVideoRepository.findById(videoId).orElseThrow(); OpalFinal activeFinal = opalFinalRepository.findByVideo_VideoIdAndIsActiveTrue(videoId) .orElse(null); if (activeFinal == null) { OpalDraft latestDraft = opalDraftRepository.findByVideo_VideoIdOrderByVersionNoDesc(videoId) .stream().findFirst() .orElseThrow(() -> new IllegalArgumentException("No active final script and no drafts found. Please generate a draft first.")); activeFinal = new OpalFinal(); activeFinal.setVideo(video); activeFinal.setDraft(latestDraft); activeFinal.setIsActive(true); activeFinal.setFinalScriptText(""); activeFinal = opalFinalRepository.save(activeFinal); } // 1. Fetch updated final script from external source (GDoc) String updatedFinalScript = externalApiService.fetchFinalScript(); // 2. Update and save active final script activeFinal.setFinalScriptText(updatedFinalScript); opalFinalRepository.save(activeFinal); // 3. Generate asset metadata var assetMap = externalApiService.generateFinalAsset(activeFinal.getFinalScriptText()); OpalFinalAsset asset = new OpalFinalAsset(); asset.setOpalFinal(activeFinal); try { asset.setAssetJson(objectMapper.writeValueAsString(assetMap)); asset.setTitle((String) assetMap.get("title")); asset.setSummary((String) assetMap.get("summary")); asset.setTimeline(objectMapper.writeValueAsString(assetMap.get("timeline"))); asset.setVideoPrompt((String) assetMap.get("video_prompt")); asset.setImageUrls(objectMapper.writeValueAsString(assetMap.get("image_urls"))); } catch (Exception e) { throw new RuntimeException("JSON processing error", e); } video.setStatus("FINALIZED"); return opalFinalAssetRepository.save(asset); } public boolean toggleComplete(Long videoId) { YtVideo video = ytVideoRepository.findById(videoId) .orElseThrow(() -> new IllegalArgumentException("Video not found")); boolean newState = video.getIsCompleted() == null ? true : !video.getIsCompleted(); video.setIsCompleted(newState); ytVideoRepository.save(video); return newState; } public void updateFinalAsset(Long videoId, String newText) { OpalFinal opalFinal = opalFinalRepository.findByVideo_VideoIdAndIsActiveTrue(videoId) .orElseThrow(() -> new IllegalArgumentException("No active final asset found for videoId: " + videoId)); opalFinal.setFinalScriptText(newText); opalFinalRepository.save(opalFinal); } public void generateOpening(Long videoId, String docId) { YtVideo video = ytVideoRepository.findById(videoId) .orElseThrow(() -> new IllegalArgumentException("Video not found")); String openingScript = externalApiService.fetchOpeningScript(docId); video.setOpeningScript(openingScript); ytVideoRepository.save(video); } public void resetOpening(Long videoId) { YtVideo video = ytVideoRepository.findById(videoId) .orElseThrow(() -> new IllegalArgumentException("Video not found")); video.setOpeningScript(null); ytVideoRepository.save(video); } }