동의 게이트와 런타임 안정화
This commit is contained in:
parent
0eb7d925ed
commit
0ec266a761
34 changed files with 1186 additions and 158 deletions
|
|
@ -35,6 +35,7 @@ import {
|
|||
type SessionDetailResponse,
|
||||
type SessionStage,
|
||||
} from "../lib/api";
|
||||
import { useAuth } from "../lib/auth";
|
||||
import { formatElapsed, formatTimecode, clamp01 } from "../lib/format";
|
||||
import { SlideToEnd } from "./session/SlideToEnd";
|
||||
import "./session/session.css";
|
||||
|
|
@ -377,6 +378,7 @@ function expressionForSession({
|
|||
export default function Session() {
|
||||
const { sessionId: routeId } = useParams<{ sessionId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { user, acceptConsent } = useAuth();
|
||||
|
||||
const routeParam = routeId ?? "";
|
||||
const routeIsSessionId = useMemo(() => looksLikeSessionId(routeParam), [routeParam]);
|
||||
|
|
@ -400,6 +402,8 @@ export default function Session() {
|
|||
const [liveSessionId, setLiveSessionId] = useState<string | null>(null);
|
||||
const [starting, setStarting] = useState(false);
|
||||
const [startError, setStartError] = useState<string | null>(null);
|
||||
const [consentChecked, setConsentChecked] = useState(false);
|
||||
const [consentBusy, setConsentBusy] = useState(false);
|
||||
|
||||
// ── 회기/대화 상태 ──
|
||||
const [stage, setStage] = useState<SessionStage>("라포");
|
||||
|
|
@ -656,13 +660,34 @@ export default function Session() {
|
|||
);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
setStartError("세션을 열지 못했습니다. 잠시 뒤 다시 시도해 주세요.");
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.detail === "consent_required") {
|
||||
setStartError("개인정보 및 연습 기록 처리 동의 후 회기를 시작할 수 있습니다.");
|
||||
} else {
|
||||
setStartError("세션을 열지 못했습니다. 잠시 뒤 다시 시도해 주세요.");
|
||||
}
|
||||
} finally {
|
||||
setStarting(false);
|
||||
}
|
||||
}, [personaCode, personaSummary, pushSignal]);
|
||||
|
||||
const handleAcceptConsent = useCallback(async () => {
|
||||
if (!consentChecked) {
|
||||
setStartError("동의 항목을 확인해야 회기를 시작할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
setConsentBusy(true);
|
||||
setStartError(null);
|
||||
try {
|
||||
await acceptConsent();
|
||||
pushSignal("pos", "동의 확인");
|
||||
} catch {
|
||||
setStartError("동의 상태를 저장하지 못했습니다. 잠시 뒤 다시 시도해 주세요.");
|
||||
} finally {
|
||||
setConsentBusy(false);
|
||||
}
|
||||
}, [acceptConsent, consentChecked, pushSignal]);
|
||||
|
||||
const appendServerClientReply = useCallback(
|
||||
(replyText: string | null, at = elapsed) => {
|
||||
if (!replyText) {
|
||||
|
|
@ -1256,10 +1281,13 @@ export default function Session() {
|
|||
sending;
|
||||
const elapsedLabel = formatElapsed(elapsed);
|
||||
const personaIsUsable = personaSummary ? isUsablePersona(personaSummary) : false;
|
||||
const canStartSession = personaLoadState === "ready" && personaIsUsable;
|
||||
const consentRequired = user?.role === "learner" && user.consentAt == null;
|
||||
const canStartSession = personaLoadState === "ready" && personaIsUsable && !consentRequired;
|
||||
const prestartTitle = canStartSession
|
||||
? `${clientName}님과의 회기를 시작할까요?`
|
||||
: personaLoadState === "ready" && personaSummary
|
||||
: consentRequired
|
||||
? "동의 확인 후 회기를 시작할 수 있습니다."
|
||||
: personaLoadState === "ready" && personaSummary
|
||||
? "이 내담자는 현재 연습에 사용할 수 없습니다."
|
||||
: "연습 대상 정보를 확인하고 있습니다.";
|
||||
const personaStatusMessage =
|
||||
|
|
@ -1371,6 +1399,29 @@ export default function Session() {
|
|||
<p className="sx-prestart__note">{personaStatusMessage}</p>
|
||||
) : null}
|
||||
{startError ? <p className="sx-prestart__err">{startError}</p> : null}
|
||||
{consentRequired ? (
|
||||
<div className="sx-consent">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consentChecked}
|
||||
onChange={(event) => setConsentChecked(event.currentTarget.checked)}
|
||||
/>
|
||||
<span>
|
||||
상담 연습 기록 저장, 개인정보 마스킹 후 평가 AI 처리, 회기 리뷰 생성을
|
||||
확인합니다.
|
||||
</span>
|
||||
</label>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleAcceptConsent}
|
||||
disabled={!consentChecked || consentBusy}
|
||||
leading={<Icon name="check" size={15} />}
|
||||
>
|
||||
{consentBusy ? "저장 중" : "동의 저장"}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="sx-prestart__actions">
|
||||
<Button
|
||||
size="lg"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue