한신대 피드백 개선팩 반영
This commit is contained in:
parent
5a9c110c11
commit
6b6241f468
25 changed files with 1247 additions and 94 deletions
|
|
@ -4006,6 +4006,8 @@ export interface components {
|
|||
crisis_resource?: components["schemas"]["CrisisResourceResponse"] | null;
|
||||
/** Effective Openness */
|
||||
effective_openness: number;
|
||||
/** Output Error */
|
||||
output_error?: string | null;
|
||||
/**
|
||||
* Safety Flagged
|
||||
* @default false
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ export interface SessionStreamDone {
|
|||
crisis_kind?: string;
|
||||
crisis_resource?: CrisisResource | null;
|
||||
conversation_stopped?: boolean;
|
||||
output_error?: string | null;
|
||||
}
|
||||
|
||||
function safeParse(data: string): unknown {
|
||||
|
|
@ -346,6 +347,7 @@ export async function openSessionStream(
|
|||
crisis_kind: parsed.crisis_kind,
|
||||
crisis_resource: parsed.crisis_resource,
|
||||
conversation_stopped: parsed.conversation_stopped,
|
||||
output_error: parsed.output_error,
|
||||
};
|
||||
handlers.onDone?.(donePayload);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1503,6 +1503,11 @@ export default function Session() {
|
|||
if (done.conversation_stopped || done.crisis_resource) {
|
||||
conversationStopped = true;
|
||||
}
|
||||
const qualityRetryable = done.output_error === "client_reply_quality_retryable";
|
||||
if (qualityRetryable) {
|
||||
setTurnError("내담자 응답 품질을 확인하지 못했습니다. 발화를 조금 다듬어 다시 시도해 주세요.");
|
||||
pushSignal("warn", "내담자 응답 재시도 필요");
|
||||
}
|
||||
|
||||
if (clientId != null) {
|
||||
const id = clientId;
|
||||
|
|
@ -1528,12 +1533,12 @@ export default function Session() {
|
|||
setUtterances((prev) =>
|
||||
prev.map((u) => (u.id === learnerId ? { ...u, turnSeq: done.turn_seq } : u)),
|
||||
);
|
||||
if (!conversationStopped) {
|
||||
if (!conversationStopped && !qualityRetryable) {
|
||||
pushSignal("warn", "내담자 응답 없음");
|
||||
}
|
||||
}
|
||||
setAvatarState("listening");
|
||||
if (!conversationStopped) {
|
||||
if (!conversationStopped && !qualityRetryable) {
|
||||
void requestLiveCoach({
|
||||
learnerText: text,
|
||||
clientReply,
|
||||
|
|
|
|||
|
|
@ -115,6 +115,35 @@ function renderSummary(summary: string) {
|
|||
});
|
||||
}
|
||||
|
||||
function displayReviewSummary(summary: string) {
|
||||
if (/(?:사유:\s*)?(?:engine_error|session evaluation timeout|timeout|RuntimeError)/i.test(summary)) {
|
||||
return "저장된 축어록은 확인했지만 deep-loop 평가 AI 산출물을 표시하지 못했습니다. AI 평가 재시도가 필요합니다.";
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
function displayEvaluationRetryError(message: string) {
|
||||
if (!message.trim()) return "AI 평가를 다시 실행하지 못했습니다.";
|
||||
return "AI 평가 재시도를 완료하지 못했습니다. 잠시 뒤 다시 실행해 주세요.";
|
||||
}
|
||||
|
||||
const REVIEW_DISPLAY_PLACEHOLDERS: Record<string, string> = {
|
||||
"[NAME]": "익명 내담자",
|
||||
"[ORG]": "소속 기관",
|
||||
"[PHONE]": "연락처",
|
||||
"[EMAIL]": "이메일",
|
||||
"[RRN]": "주민등록번호",
|
||||
"[ADDRESS]": "주소",
|
||||
};
|
||||
|
||||
function displayTranscriptText(text: string) {
|
||||
let next = text;
|
||||
for (const [placeholder, label] of Object.entries(REVIEW_DISPLAY_PLACEHOLDERS)) {
|
||||
next = next.split(placeholder).join(label);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function shouldPollReviewReady(data: SessionReviewResponse) {
|
||||
return (
|
||||
!data.reviewReady &&
|
||||
|
|
@ -298,6 +327,27 @@ function SupervisorCallout({ note }: { note: ReviewNote }) {
|
|||
);
|
||||
}
|
||||
|
||||
function EvaluationScopeNotice() {
|
||||
return (
|
||||
<div className="sr-eval-scope" aria-label="AI 평가 범위 안내">
|
||||
<div className="sr-eval-scope__title">
|
||||
<Icon name="info" size={14} strokeWidth={2.2} />
|
||||
<b>AI 평가 범위</b>
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>현재 구현</dt>
|
||||
<dd>fast-loop는 턴 직후 발화를 점검하고, deep-loop는 종료 후 회기 전체를 재분석합니다.</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>임상팀 확정 필요</dt>
|
||||
<dd>점수 해석 기준, 교수자 최종 판정 문구, 기관별 루브릭 가중치는 아직 확정 전입니다.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function JumpablePoint({
|
||||
point,
|
||||
turns,
|
||||
|
|
@ -838,13 +888,13 @@ export default function SessionReview() {
|
|||
}
|
||||
setReviewReloadSeq((seq) => seq + 1);
|
||||
} catch (err) {
|
||||
setEvaluationRetryError(
|
||||
const message =
|
||||
err instanceof ApiError
|
||||
? err.detail
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: "AI 평가를 다시 실행하지 못했습니다.",
|
||||
);
|
||||
: "";
|
||||
setEvaluationRetryError(displayEvaluationRetryError(message));
|
||||
} finally {
|
||||
setEvaluationRetrying(false);
|
||||
}
|
||||
|
|
@ -1042,7 +1092,7 @@ export default function SessionReview() {
|
|||
<Card className="sr-overview">
|
||||
<Kicker>리뷰 요약</Kicker>
|
||||
<p className="sr-summary">
|
||||
{renderSummary(data.summary)}
|
||||
{renderSummary(displayReviewSummary(data.summary))}
|
||||
</p>
|
||||
<div className="sr-readiness" aria-label="리뷰 생성 상태">
|
||||
<span>
|
||||
|
|
@ -1250,7 +1300,7 @@ export default function SessionReview() {
|
|||
(turn.speaker === "client" ? " sr-turn__said--client" : "")
|
||||
}
|
||||
>
|
||||
{turn.text}
|
||||
{displayTranscriptText(turn.text)}
|
||||
</p>
|
||||
{turn.note ? <SupervisorCallout note={turn.note} /> : null}
|
||||
</div>
|
||||
|
|
@ -1279,6 +1329,7 @@ export default function SessionReview() {
|
|||
{teacherReviewStatusLabel}
|
||||
</Badge>
|
||||
</div>
|
||||
<EvaluationScopeNotice />
|
||||
<label className="sr-teacher-review__note">
|
||||
<span>검토 메모</span>
|
||||
<textarea
|
||||
|
|
@ -1491,7 +1542,7 @@ export default function SessionReview() {
|
|||
<div className={`sr-feedback${data.clientFeedback ? " sr-feedback--filled" : ""}`}>
|
||||
<div className="sr-feedback__kicker">
|
||||
<span className="sr-technique__dot" aria-hidden="true" />
|
||||
내담자가 남긴 것
|
||||
마지막 내담자 반응
|
||||
</div>
|
||||
{data.clientFeedback ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -311,6 +311,45 @@
|
|||
.sr-teacher-review__head--sub {
|
||||
margin-bottom: var(--sp-2);
|
||||
}
|
||||
.sr-eval-scope {
|
||||
display: grid;
|
||||
gap: var(--sp-2);
|
||||
margin-bottom: var(--sp-3);
|
||||
padding: 10px 11px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-surface-2);
|
||||
}
|
||||
.sr-eval-scope__title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: var(--text-strong);
|
||||
font-size: var(--fs-xs);
|
||||
line-height: 1.35;
|
||||
}
|
||||
.sr-eval-scope dl {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
margin: 0;
|
||||
}
|
||||
.sr-eval-scope dl div {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
.sr-eval-scope dt {
|
||||
color: var(--text-strong);
|
||||
font-size: 11px;
|
||||
font-weight: 740;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.sr-eval-scope dd {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.sr-teacher-review__worksheet {
|
||||
margin-top: var(--sp-4);
|
||||
padding-top: var(--sp-4);
|
||||
|
|
@ -1468,6 +1507,7 @@
|
|||
[data-theme="dark"] .sr-readiness span,
|
||||
[data-theme="dark"] .sr-empty,
|
||||
[data-theme="dark"] .sr-chip-toggle,
|
||||
[data-theme="dark"] .sr-eval-scope,
|
||||
[data-theme="dark"] .sr-ws-item,
|
||||
[data-theme="dark"] .sr-prepost__field input,
|
||||
[data-theme="dark"] .sr-feedback,
|
||||
|
|
|
|||
|
|
@ -906,6 +906,8 @@
|
|||
}
|
||||
.sx-transcript__scroll {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: none;
|
||||
max-height: none;
|
||||
flex: 1;
|
||||
min-height: 160px;
|
||||
|
|
@ -913,6 +915,10 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sx-transcript__scroll::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.sx-transcript__empty {
|
||||
min-height: 100%;
|
||||
display: grid;
|
||||
|
|
@ -3686,6 +3692,9 @@
|
|||
.sx-mobile-context__row span {
|
||||
padding: 7px 6px;
|
||||
}
|
||||
.sx-page--active .sx-transcript__scroll {
|
||||
min-height: 152px;
|
||||
}
|
||||
.sx-page--active .sx-controlbar {
|
||||
padding: 8px;
|
||||
gap: 8px;
|
||||
|
|
@ -3833,6 +3842,25 @@
|
|||
.sx-page--active .sx-stage__now {
|
||||
display: none;
|
||||
}
|
||||
.sx-page--active .sx-transcript__scroll {
|
||||
min-height: 110px;
|
||||
}
|
||||
.sx-page--active .sx-transcript__empty {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 2px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.sx-page--active .sx-transcript__empty-dot {
|
||||
display: none;
|
||||
}
|
||||
.sx-page--active .sx-transcript__empty b {
|
||||
font-size: 12px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.sx-page--active .sx-transcript__empty span {
|
||||
font-size: 11px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.sx-page--active .sx-mic-block__ms {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -3900,14 +3928,18 @@
|
|||
height: 58px !important;
|
||||
}
|
||||
.sx-page--active .sx-transcript {
|
||||
padding: 6px 8px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
.sx-page--active .sx-transcript__head {
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.sx-page--active .sx-transcript__empty {
|
||||
gap: 8px;
|
||||
padding: 8px 6px;
|
||||
}
|
||||
.sx-page--active .sx-compose {
|
||||
margin-top: 5px;
|
||||
padding-top: 5px;
|
||||
margin-top: 3px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
.sx-page--active .sx-compose textarea {
|
||||
min-height: 36px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue