import { API_BASE_URL } from "@config/config_frontend"; type Source = { path: string; excerpt: string }; type ChatAnswer = { answer: string; sources: Source[]; detail?: string }; const chatIcon = ``; export function buildKnowledgeChat(): HTMLElement { const wrap = document.createElement("div"); wrap.className = "b01-knowledge-chat"; const toggle = document.createElement("button"); toggle.type = "button"; toggle.className = "b01-knowledge-chat__toggle"; toggle.innerHTML = chatIcon; toggle.setAttribute("aria-label", "챗봇 열기"); toggle.title = "챗봇"; toggle.setAttribute("aria-expanded", "false"); const panel = document.createElement("section"); panel.className = "b01-knowledge-chat__panel"; panel.hidden = true; panel.id = "b01-knowledge-chat-panel"; panel.setAttribute("aria-label", "챗봇"); toggle.setAttribute("aria-controls", panel.id); const header = document.createElement("header"); const avatar = document.createElement("span"); avatar.className = "b01-knowledge-chat__avatar"; avatar.innerHTML = chatIcon; const title = document.createElement("strong"); title.textContent = "챗봇"; const close = document.createElement("button"); close.type = "button"; close.className = "b01-knowledge-chat__close"; close.textContent = "×"; close.setAttribute("aria-label", "챗봇 닫기"); header.append(avatar, title, close); const messages = document.createElement("div"); messages.className = "b01-knowledge-chat__messages"; messages.setAttribute("role", "log"); messages.setAttribute("aria-live", "polite"); const welcome = document.createElement("p"); welcome.className = "b01-knowledge-chat__reply"; welcome.textContent = "안녕하세요! 법령이나 지침에 대해 질문해 주세요. 근거 문서도 함께 보여드릴게요."; messages.append(welcome); const form = document.createElement("form"); form.className = "b01-knowledge-chat__form"; const input = document.createElement("input"); input.type = "text"; input.required = true; input.maxLength = 500; input.placeholder = "메시지를 입력하세요"; input.setAttribute("aria-label", "질문"); const submit = document.createElement("button"); submit.type = "submit"; submit.textContent = "보내기"; form.append(input, submit); panel.append(header, messages, form); wrap.append(toggle, panel); const setOpen = (open: boolean): void => { panel.hidden = !open; toggle.setAttribute("aria-expanded", String(!panel.hidden)); toggle.setAttribute("aria-label", open ? "챗봇 닫기" : "챗봇 열기"); if (open) input.focus(); else toggle.focus(); }; toggle.addEventListener("click", () => setOpen(Boolean(panel.hidden))); close.addEventListener("click", () => setOpen(false)); form.addEventListener("submit", async (event) => { event.preventDefault(); const question = input.value.trim(); if (question.length < 2) return; const userLine = document.createElement("p"); userLine.className = "b01-knowledge-chat__user"; userLine.textContent = question; messages.append(userLine); input.value = ""; input.disabled = submit.disabled = true; const reply = document.createElement("div"); reply.className = "b01-knowledge-chat__reply"; reply.textContent = "원문을 찾는 중입니다…"; messages.append(reply); messages.scrollTop = messages.scrollHeight; try { const response = await fetch(`${API_BASE_URL}/dashboard/knowledge-chat`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question }), }); const result = (await response.json()) as ChatAnswer; if (!response.ok) throw new Error(result.detail || "답변을 받지 못했습니다."); reply.textContent = result.answer; if (result.sources.length) { const sources = document.createElement("details"); const summary = document.createElement("summary"); summary.textContent = `근거 원문 ${result.sources.length}건`; sources.append(summary); result.sources.forEach((source, index) => { const item = document.createElement("details"); const title = document.createElement("summary"); title.textContent = `[${index + 1}] ${source.path}`; const excerpt = document.createElement("pre"); excerpt.textContent = source.excerpt; item.append(title, excerpt); sources.append(item); }); reply.append(sources); } } catch (error) { reply.textContent = error instanceof Error ? error.message : "답변을 받지 못했습니다."; } finally { input.disabled = submit.disabled = false; input.focus(); messages.scrollTop = messages.scrollHeight; } }); return wrap; }