Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2679,6 +2679,9 @@
},
"audioFileUrl" : {
"type" : "string"
},
"expectedSignal" : {
"type" : "string"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,24 @@ public class InterviewMessageService {
private final ApplicationEventPublisher events;

public List<MessageResult> list(Long userId, Long sessionId) {
ownedSession(userId, sessionId);
InterviewSession session = ownedSession(userId, sessionId);
// 종료된 세션에서만 expected_signal 노출(라이브/대기 중엔 정답 유출 방지).
boolean revealExpectedSignal =
session.getStatus() != SessionStatus.IN_PROGRESS
&& session.getStatus() != SessionStatus.READY;
return messageRepository.findBySession_IdOrderBySequenceNumberAsc(sessionId).stream()
.map(this::toResultWithAudioUrls)
.map(m -> toResultWithAudioUrls(m, revealExpectedSignal))
.toList();
}

// 재생용 presigned URL 동봉: 질문 TTS(SUCCEEDED) + 음성 답변 원본.
// presign 실패가 메시지 조회 전체를 깨뜨리지 않도록 개별 try/catch.
private MessageResult toResultWithAudioUrls(InterviewMessage m) {
private MessageResult toResultWithAudioUrls(InterviewMessage m, boolean revealExpectedSignal) {
String ttsUrl = m.getTtsStatus() == TtsStatus.SUCCEEDED
? presign(m.getTtsAudioPath()) : null;
String audioUrl = presign(m.getAudioFilePath());
return MessageResult.of(m, ttsUrl, audioUrl);
String expectedSignal = revealExpectedSignal ? m.getExpectedSignal() : null;
return MessageResult.of(m, ttsUrl, audioUrl, expectedSignal);
}

private String presign(String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,21 @@ public record MessageResult(
String targetEvidence,
// 재생용 presigned URL. 조회(list) 경로에서만 채우고, 그 외에는 null.
String ttsAudioUrl,
String audioFileUrl
// expectedSignal 은 의도적으로 제외 — 정답 유출 방지(라이브 비노출).
String audioFileUrl,
// 질문이 기대한 핵심(평가 관점). 라이브 중엔 null(정답 유출 방지),
// 종료된 세션 조회에서만 채워 피드백 학습용으로 노출.
String expectedSignal
) {
public static MessageResult of(InterviewMessage m) {
return of(m, null, null);
return of(m, null, null, null);
}

public static MessageResult of(InterviewMessage m, String ttsAudioUrl, String audioFileUrl) {
return of(m, ttsAudioUrl, audioFileUrl, null);
}

public static MessageResult of(
InterviewMessage m, String ttsAudioUrl, String audioFileUrl, String expectedSignal) {
return new MessageResult(
m.getId(),
m.getSession().getId(),
Expand All @@ -47,7 +54,8 @@ public static MessageResult of(InterviewMessage m, String ttsAudioUrl, String au
m.getCategory(),
m.getTargetEvidence(),
ttsAudioUrl,
audioFileUrl
audioFileUrl,
expectedSignal
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ public record MessageResponse(
String targetEvidence,
// 재생용 presigned URL (질문 TTS / 음성 답변 원본). 조회 응답에서만 채움.
String ttsAudioUrl,
String audioFileUrl
// expectedSignal 은 노출하지 않음 — 자기연습 시 정답 유출 방지.
String audioFileUrl,
// 질문이 기대한 핵심(평가 관점). 종료된 세션에서만 채워짐(라이브 중엔 null — 정답 유출 방지).
String expectedSignal
) {
public static MessageResponse from(MessageResult r) {
return new MessageResponse(
Expand All @@ -43,7 +44,8 @@ public static MessageResponse from(MessageResult r) {
r.category(),
r.targetEvidence(),
r.ttsAudioUrl(),
r.audioFileUrl()
r.audioFileUrl(),
r.expectedSignal()
);
}
}
7 changes: 7 additions & 0 deletions frontend/src/features/interview/ui/live/QuestionBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ export function QuestionBubble({
</div>
)}
</div>
{/* expected_signal 은 종료된 세션에서만 내려옴(피드백 학습용). 라이브엔 비노출. */}
{message.expectedSignal && (
<div className="rounded-md border border-dashed border-border bg-surface px-3 py-2 text-caption text-fg-muted">
<span className="font-medium text-fg">이 질문이 본 핵심</span> ·{' '}
{message.expectedSignal}
</div>
)}
</div>
</div>
)
Expand Down
1 change: 1 addition & 0 deletions frontend/src/shared/api/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,7 @@ export interface components {
targetEvidence?: string;
ttsAudioUrl?: string;
audioFileUrl?: string;
expectedSignal?: string;
};
VoiceStreamBeginResponse: {
/** Format: int64 */
Expand Down