96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
import { API_BASE_URL } from "@config/config_frontend";
|
|
|
|
type Source = { path: string; excerpt: string };
|
|
type ChatAnswer = { answer: string; sources: Source[]; detail?: string };
|
|
|
|
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.textContent = "원문 안내";
|
|
toggle.setAttribute("aria-expanded", "false");
|
|
const panel = document.createElement("section");
|
|
panel.className = "b01-knowledge-chat__panel";
|
|
panel.hidden = true;
|
|
panel.setAttribute("aria-label", "지식DB 원문 안내 채팅");
|
|
const header = document.createElement("header");
|
|
header.textContent = "지식DB 원문 안내";
|
|
const messages = document.createElement("div");
|
|
messages.className = "b01-knowledge-chat__messages";
|
|
messages.setAttribute("role", "log");
|
|
messages.setAttribute("aria-live", "polite");
|
|
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);
|
|
|
|
toggle.addEventListener("click", () => {
|
|
panel.hidden = !panel.hidden;
|
|
toggle.setAttribute("aria-expanded", String(!panel.hidden));
|
|
if (!panel.hidden) input.focus();
|
|
});
|
|
|
|
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;
|
|
}
|