diff --git a/sundol-backend/src/main/java/com/sundol/controller/NoteController.java b/sundol-backend/src/main/java/com/sundol/controller/NoteController.java index 28baacc..82ed969 100644 --- a/sundol-backend/src/main/java/com/sundol/controller/NoteController.java +++ b/sundol-backend/src/main/java/com/sundol/controller/NoteController.java @@ -133,7 +133,8 @@ public class NoteController { noteRepository.updateContent(id, polished + "\n\n--- 요약 생성 중... ---"); String summary = summarizeTranscription(polished); - String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished; + String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished + + modelFooter(null); String newTitle = generateAudioTitle(summary, java.time.LocalDateTime.now()); noteRepository.update(id, null, newTitle, result, null); noteRepository.updateNoteType(id, isAudio ? "AUDIO" : "TEXT"); @@ -202,11 +203,13 @@ public class NoteController { private void transcribeAsync(String noteId, Path audioFile, String inputTitle) throws IOException, InterruptedException { // === Step 1: STT (OpenRouter Gemini) === String rawResult = null; + String sttModel = null; if (openRouterApiKey != null && !openRouterApiKey.isBlank()) { try { noteRepository.updateContent(noteId, "Gemini로 음성 변환 중..."); rawResult = transcribeWithOpenRouter(audioFile); + sttModel = openRouterModel; log.info("OpenRouter STT: {} chars", rawResult != null ? rawResult.length() : 0); } catch (Exception e) { log.warn("OpenRouter STT failed: {}", e.getMessage()); @@ -219,6 +222,7 @@ public class NoteController { try { noteRepository.updateContent(noteId, "Gemma로 음성 변환 중..."); rawResult = transcribeWithGemma(audioFile); + sttModel = "gemma4:e4b"; } catch (Exception e) { log.error("All STT failed for note {}", noteId, e); noteRepository.updateContent(noteId, "모든 음성 변환 실패: " + e.getMessage()); @@ -257,7 +261,8 @@ public class NoteController { log.info("Summary complete: {} chars", summary.length()); // 최종 결과 저장 - String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished; + String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished + + modelFooter(sttModel); String finalTitle = inputTitle.equals("음성 변환 중...") ? generateAudioTitle(summary, java.time.LocalDateTime.now()) : inputTitle; @@ -271,8 +276,18 @@ public class NoteController { } /** - * Gemma 4 E4B를 사용하여 오디오 파일을 텍스트로 변환 + * 노트 하단에 사용 모델을 표기하는 푸터를 만든다. + * sttModel이 있으면 음성변환 모델도 함께 표기하고, 없으면 교정·요약 모델만 표기한다. + * 모델명은 genAiService.getDefaultModel()/openRouterModel에서 가져오므로 설정 변경 시 자동 반영된다. */ + private String modelFooter(String sttModel) { + String polishModel = genAiService.getDefaultModel(); + if (sttModel != null && !sttModel.isBlank()) { + return "\n\n---\n\n*🤖 음성변환: " + sttModel + " · 교정·요약: " + polishModel + "*"; + } + return "\n\n---\n\n*🤖 교정·요약: " + polishModel + "*"; + } + /** * 오디오 파일을 wav로 변환한다 (Ollama 호환성). */ diff --git a/sundol-backend/src/main/java/com/sundol/service/OciGenAiService.java b/sundol-backend/src/main/java/com/sundol/service/OciGenAiService.java index 82afa4c..704b814 100644 --- a/sundol-backend/src/main/java/com/sundol/service/OciGenAiService.java +++ b/sundol-backend/src/main/java/com/sundol/service/OciGenAiService.java @@ -13,6 +13,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -84,21 +85,28 @@ public class OciGenAiService { modelId = defaultModel; } + Map chatRequest = new LinkedHashMap<>(); + chatRequest.put("apiFormat", "GENERIC"); + chatRequest.put("messages", List.of( + Map.of("role", "SYSTEM", "content", List.of(Map.of("type", "TEXT", "text", systemMessage))), + Map.of("role", "USER", "content", List.of(Map.of("type", "TEXT", "text", userMessage))) + )); + // OpenAI 계열(openai.gpt-5.x 등)은 maxTokens / temperature(0.3)를 거부한다. + // maxCompletionTokens를 사용하고, temperature는 기본값(1)만 허용되므로 생략한다. + if (modelId.startsWith("openai.")) { + chatRequest.put("maxCompletionTokens", 65536); + } else { + chatRequest.put("maxTokens", 65536); + chatRequest.put("temperature", 0.3); + } + Map payload = Map.of( "compartmentId", compartment, "servingMode", Map.of( "servingType", "ON_DEMAND", "modelId", modelId ), - "chatRequest", Map.of( - "apiFormat", "GENERIC", - "messages", List.of( - Map.of("role", "SYSTEM", "content", List.of(Map.of("type", "TEXT", "text", systemMessage))), - Map.of("role", "USER", "content", List.of(Map.of("type", "TEXT", "text", userMessage))) - ), - "maxTokens", 65536, - "temperature", 0.3 - ) + "chatRequest", chatRequest ); String body = objectMapper.writeValueAsString(payload);