개선관리 요구사항과 Google 로그인을 완료
This commit is contained in:
parent
cc0a15b7c6
commit
2a39636163
112 changed files with 10166 additions and 527 deletions
|
|
@ -19,6 +19,7 @@ import "./alliance-pulse.css";
|
|||
|
||||
interface PulseTurn {
|
||||
id: string;
|
||||
turn_id?: string | null;
|
||||
ts: string;
|
||||
speaker: string;
|
||||
who: string;
|
||||
|
|
@ -29,6 +30,7 @@ interface AlliancePulseCardProps {
|
|||
sessionId: string;
|
||||
turns: PulseTurn[];
|
||||
isSupervisorView: boolean;
|
||||
feedbackEnabled: boolean;
|
||||
onJumpToTurn: (turnId: string) => void;
|
||||
}
|
||||
|
||||
|
|
@ -206,7 +208,16 @@ function EvidencePicker({
|
|||
onChange: (turnIds: string[]) => void;
|
||||
scope: "learner" | "supervisor";
|
||||
}) {
|
||||
const eligible = useMemo(() => turns.slice(-12), [turns]);
|
||||
const eligible = useMemo(
|
||||
() =>
|
||||
turns
|
||||
.flatMap((turn) => {
|
||||
const persistedId = turn.turn_id?.trim();
|
||||
return persistedId ? [{ turn, persistedId }] : [];
|
||||
})
|
||||
.slice(-12),
|
||||
[turns],
|
||||
);
|
||||
const summaryLabel =
|
||||
scope === "learner" ? "내 판단의 근거 장면 선택" : "교수자 판정 근거 장면 선택";
|
||||
|
||||
|
|
@ -226,12 +237,12 @@ function EvidencePicker({
|
|||
</summary>
|
||||
{eligible.length > 0 ? (
|
||||
<div className="ap-evidence-picker__list">
|
||||
{eligible.map((turn) => (
|
||||
{eligible.map(({ turn, persistedId }) => (
|
||||
<label key={turn.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.includes(turn.id)}
|
||||
onChange={() => toggle(turn.id)}
|
||||
checked={selected.includes(persistedId)}
|
||||
onChange={() => toggle(persistedId)}
|
||||
/>
|
||||
<span className="ap-evidence-picker__time">{turn.ts}</span>
|
||||
<span>
|
||||
|
|
@ -242,7 +253,7 @@ function EvidencePicker({
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p>선택할 수 있는 저장 발화가 없습니다.</p>
|
||||
<p>영속 ID가 확인된 저장 발화가 없습니다.</p>
|
||||
)}
|
||||
</details>
|
||||
);
|
||||
|
|
@ -360,6 +371,25 @@ function LockedWaitingState({ pulse }: { pulse: AlliancePulse }) {
|
|||
);
|
||||
}
|
||||
|
||||
function SelfOnlyState({ pulse }: { pulse: AlliancePulse }) {
|
||||
return (
|
||||
<div className="ap-waiting ap-self-only" aria-label="저장된 치료 동맹 자기평가">
|
||||
<div className="ap-waiting__copy">
|
||||
<b>저장한 내 치료 동맹 자기평가</b>
|
||||
<p>내가 직접 잠근 세 축만 보존합니다. AI·교수자 파생 비교는 표시하지 않습니다.</p>
|
||||
</div>
|
||||
<dl className="ap-locked-scores">
|
||||
{DIMENSIONS.map((dimension) => (
|
||||
<div key={dimension}>
|
||||
<dt>{DIMENSION_COPY[dimension].short}</dt>
|
||||
<dd>{scoreText(pulse.self_scores?.[dimension] ?? null)}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ComparisonView({
|
||||
pulse,
|
||||
onJumpToTurn,
|
||||
|
|
@ -525,6 +555,7 @@ export function AlliancePulseCard({
|
|||
sessionId,
|
||||
turns,
|
||||
isSupervisorView,
|
||||
feedbackEnabled,
|
||||
onJumpToTurn,
|
||||
}: AlliancePulseCardProps) {
|
||||
const [loadState, setLoadState] = useState<LoadState>("loading");
|
||||
|
|
@ -559,12 +590,16 @@ export function AlliancePulseCard({
|
|||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pulse || (pulse.status !== "awaiting_agents" && pulse.status !== "processing")) {
|
||||
if (
|
||||
!feedbackEnabled ||
|
||||
!pulse ||
|
||||
(pulse.status !== "awaiting_agents" && pulse.status !== "processing")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const timer = window.setTimeout(() => void load(), POLL_INTERVAL_MS);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [load, pulse]);
|
||||
}, [feedbackEnabled, load, pulse]);
|
||||
|
||||
const submitSelfAssessment = async () => {
|
||||
if (!isCompleteDraft(draft) || submitting) return;
|
||||
|
|
@ -606,8 +641,8 @@ export function AlliancePulseCard({
|
|||
const revealReady = Boolean(
|
||||
pulse?.learner_locked_at && pulse?.revealed_at,
|
||||
);
|
||||
const status = pulse ? pulseStatusCopy(pulse) : null;
|
||||
const learnerEvidenceRequired = turns.length > 0;
|
||||
const status = feedbackEnabled && pulse ? pulseStatusCopy(pulse) : null;
|
||||
const learnerEvidenceRequired = turns.some((turn) => Boolean(turn.turn_id?.trim()));
|
||||
const canSubmit =
|
||||
isCompleteDraft(draft) &&
|
||||
(!learnerEvidenceRequired || evidenceTurnIds.length > 0) &&
|
||||
|
|
@ -642,8 +677,16 @@ export function AlliancePulseCard({
|
|||
content = (
|
||||
<div className="ap-self-form">
|
||||
<div className="ap-self-form__intro">
|
||||
<b>AI 관점을 보기 전에 먼저 스스로 판단합니다</b>
|
||||
<p>세 축을 따로 평가하세요. 제출하면 값은 잠기며 이후 관점에 맞춰 수정할 수 없습니다.</p>
|
||||
<b>
|
||||
{feedbackEnabled
|
||||
? "AI 관점을 보기 전에 먼저 스스로 판단합니다"
|
||||
: "목표·과업·유대를 내가 직접 기록합니다"}
|
||||
</b>
|
||||
<p>
|
||||
{feedbackEnabled
|
||||
? "세 축을 따로 평가하세요. 제출하면 값은 잠기며 이후 관점에 맞춰 수정할 수 없습니다."
|
||||
: "세 축을 따로 평가해 저장합니다. AI·교수자 파생 비교는 표시하지 않습니다."}
|
||||
</p>
|
||||
</div>
|
||||
{DIMENSIONS.map((dimension) => (
|
||||
<AxisAssessmentControl
|
||||
|
|
@ -667,14 +710,22 @@ export function AlliancePulseCard({
|
|||
<div className="ap-self-form__commit">
|
||||
<span>
|
||||
<Icon name="shield" size={15} strokeWidth={1.8} />
|
||||
제출 뒤에는 AI 관점이 준비될 때까지 내 판단만 보입니다.
|
||||
{feedbackEnabled
|
||||
? "제출 뒤에는 AI 관점이 준비될 때까지 내 판단만 보입니다."
|
||||
: "제출 뒤에도 내가 직접 잠근 기록만 표시합니다."}
|
||||
</span>
|
||||
<Button size="sm" disabled={!canSubmit} onClick={() => void submitSelfAssessment()}>
|
||||
{submitting ? "판단 잠그는 중" : "내 판단 잠그고 관점 비교"}
|
||||
{submitting
|
||||
? "판단 잠그는 중"
|
||||
: feedbackEnabled
|
||||
? "내 판단 잠그고 관점 비교"
|
||||
: "내 판단 기록"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (!feedbackEnabled) {
|
||||
content = <SelfOnlyState pulse={pulse} />;
|
||||
} else if (!revealReady) {
|
||||
content = <LockedWaitingState pulse={pulse} />;
|
||||
} else {
|
||||
|
|
@ -697,8 +748,14 @@ export function AlliancePulseCard({
|
|||
<Card className="sr-card ap-card" aria-busy={loadState === "loading"}>
|
||||
<div className="ap-card__head">
|
||||
<div>
|
||||
<Kicker dot={false}>치료 동맹 펄스</Kicker>
|
||||
<h2>목표, 과업, 유대를 따로 봅니다</h2>
|
||||
<Kicker dot={false}>
|
||||
{feedbackEnabled ? "치료 동맹 펄스" : "치료 동맹 자기평가"}
|
||||
</Kicker>
|
||||
<h2>
|
||||
{feedbackEnabled
|
||||
? "목표, 과업, 유대를 따로 봅니다"
|
||||
: "목표, 과업, 유대를 직접 기록합니다"}
|
||||
</h2>
|
||||
</div>
|
||||
{status ? <Badge tone={status.tone}>{status.label}</Badge> : null}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue