feat(coach): AI 코칭 버튼 및 버블 팝업 대화형 조언 기능 구현

This commit is contained in:
Yun Chan 2026-08-18 22:15:19 +09:00
parent 8a34dd2afc
commit 70d7cb34a3
36 changed files with 4521 additions and 1264 deletions

View file

@ -177,16 +177,43 @@ async function routeCoachVoiceSession(page: Page, options: CoachVoiceOptions = {
);
});
await page.route(`**/api/sessions/${SESSION_ID}/live-coach`, (route) =>
route.fulfill(
await page.route(`**/api/sessions/${SESSION_ID}/live-coach`, async (route) => {
if (route.request().method() === "POST") {
await route.fulfill(
jsonRoute({
status: "ready",
tone: "pos",
focus: "emotion",
title: "공감과 탐색",
message: "내담자의 답답한 마음에 먼저 공감하고 구체적인 장면을 열린 질문으로 물어보세요.",
next_utterance: "학교 생각만 하면 답답하다고 했는데, 어떤 부분이 제일 마음에 걸려요?",
rationale: "감정 타당화 후 구체 촉발 탐색",
quota: { remaining: Math.max(0, coachRemaining - 1), max: coachMax },
credit_events: [
{
event_id: "c-001",
session_id: SESSION_ID,
turn_seq: 1,
event_type: "use",
delta: -1,
balance: Math.max(0, coachRemaining - 1),
reason: "live coaching request",
created_at: new Date().toISOString(),
},
],
}),
);
return;
}
await route.fulfill(
jsonRoute({
source: "database",
quota: { remaining: coachRemaining, max: coachMax },
credit_events: [],
events: [],
}),
),
);
);
});
}
async function openSession(page: Page) {
@ -419,4 +446,49 @@ test.describe("AI 코치·음성 UI — 고지·게이트·피드백 모드", ()
const label = await mic.getAttribute("aria-label");
expect(label ?? "").not.toBe("");
});
// usecase: AI 코칭 버튼 클릭 시 버블 팝업이 노출되고 추천 칩 및 채팅 조언을 얻을 수 있다
test("AI 코칭 버튼 클릭 시 버블 팝업이 뜨고 채팅 질문과 추천 발화 적용이 동작한다", async ({ page }) => {
await routeCoachVoiceSession(page, { coachRemaining: 3, coachMax: 3 });
await openSession(page);
const triggerBtn = page.getByRole("button", { name: /^AI 코칭, 남은 기회/ });
await expect(triggerBtn).toBeVisible();
await expect(triggerBtn).toContainText("3");
// 1. 버블 팝업 열기
await triggerBtn.click({ force: true });
const popup = page.getByRole("dialog", { name: "AI 슈퍼바이저 코칭 버블" });
await expect(popup).toBeVisible();
await expect(popup).toContainText("AI 슈퍼바이저 코칭");
await expect(popup).toContainText("기회 3/3");
// 2. 추천 칩 확인 및 클릭
const recommendChip = popup.getByRole("button", { name: "💡 다음 추천 발화" });
await expect(recommendChip).toBeVisible();
await recommendChip.dispatchEvent("click");
// 3. 조언 메시지 및 추천 발화 노출 확인
await expect(popup.locator(".sx-coach-msg--learner")).toContainText("다음 추천 발화와 방향을 알려줘");
await expect(popup.locator(".sx-coach-msg--coach")).toBeVisible();
const insertBtn = popup.getByRole("button", { name: "발화창에 넣기" });
await expect(insertBtn).toBeAttached();
// 4. [발화창에 넣기] 클릭 시 하단 발화 입력창에 텍스트 반영
await insertBtn.dispatchEvent("click");
const composer = page.getByPlaceholder("학습자 발화를 입력하세요.");
await expect(composer).not.toHaveValue("");
// 5. 직접 질문 채팅 입력 및 전송
const coachInput = popup.getByPlaceholder("코치에게 조언을 구해보세요... (Enter 전송)");
await coachInput.fill("내담자의 저항을 부드럽게 다루려면 어떻게 하나요?");
await popup.getByRole("button", { name: "코칭 질문 전송" }).dispatchEvent("click");
await expect(popup.locator(".sx-coach-msg--learner").last()).toContainText("내담자의 저항을 부드럽게 다루려면");
await expect(popup.locator(".sx-coach-msg--coach").last()).toBeVisible();
// 6. 팝업 닫기 버튼 클릭 시 닫힘 확인
await popup.getByRole("button", { name: "코칭 팝업 닫기" }).dispatchEvent("click");
await expect(popup).toBeHidden();
});
});