Merge remote-tracking branch 'origin/CODEX' into sub_laptop_8

This commit is contained in:
2026-10-06 15:29:28 +09:00
4 changed files with 112 additions and 19 deletions
@@ -13,7 +13,7 @@ from common_util.common_util_auth import verify_session
router = APIRouter(prefix="/knowledge-chat", tags=["B01_KnowledgeChat"])
KNOWLEDGE = Path(__file__).resolve().parents[1] / "resources" / "knowledge"
EXCLUDED = ("실무문서", "stmate", "다산소프트")
EXCLUDED = ("실무문서", "stmate", "다산소프트", "technical_info")
MODEL = "gemini-3.8-flash"
@@ -2,6 +2,7 @@ import { API_BASE_URL } from "@config/config_frontend";
type Source = { path: string; excerpt: string };
type ChatAnswer = { answer: string; sources: Source[]; detail?: string };
const chatIcon = `<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 5h16a5 5 0 0 1 5 5v10a5 5 0 0 1-5 5H13l-7 4v-5a5 5 0 0 1-3-4V10a5 5 0 0 1 5-5Z"/><path d="M11 19c0-5 4-8 10-8 0 6-3 10-8 10"/><path d="M11 22c2-3 5-5 9-7"/></svg>`;
export function buildKnowledgeChat(): HTMLElement {
const wrap = document.createElement("div");
@@ -9,25 +10,44 @@ export function buildKnowledgeChat(): HTMLElement {
const toggle = document.createElement("button");
toggle.type = "button";
toggle.className = "b01-knowledge-chat__toggle";
toggle.textContent = "원문 안내";
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.setAttribute("aria-label", "지식DB 원문 안내 채팅");
panel.id = "b01-knowledge-chat-panel";
panel.setAttribute("aria-label", "챗봇");
toggle.setAttribute("aria-controls", panel.id);
const header = document.createElement("header");
header.textContent = "지식DB 원문 안내";
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.placeholder = "메시지를 입력하세요";
input.setAttribute("aria-label", "질문");
const submit = document.createElement("button");
submit.type = "submit";
@@ -36,11 +56,15 @@ export function buildKnowledgeChat(): HTMLElement {
panel.append(header, messages, form);
wrap.append(toggle, panel);
toggle.addEventListener("click", () => {
panel.hidden = !panel.hidden;
const setOpen = (open: boolean): void => {
panel.hidden = !open;
toggle.setAttribute("aria-expanded", String(!panel.hidden));
if (!panel.hidden) input.focus();
});
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();
+77 -9
View File
@@ -535,16 +535,30 @@
.b01-knowledge-chat__toggle,
.b01-knowledge-chat__form button {
padding: 10px 16px;
border: 0;
border-radius: 8px;
background: var(--color-royal-amethyst, #6843a7);
color: #fff;
font: inherit;
cursor: pointer;
}
.b01-knowledge-chat__toggle {
display: grid;
width: 58px;
height: 58px;
place-items: center;
border-radius: 50%;
box-shadow: 0 4px 16px #0004;
}
.b01-knowledge-chat__toggle svg {
width: 30px;
height: 30px;
}
.b01-knowledge-chat__panel {
display: flex;
flex-direction: column;
width: min(380px, calc(100vw - 32px));
height: min(540px, calc(100vh - 104px));
border: 1px solid var(--color-border);
@@ -560,34 +574,76 @@
}
.b01-knowledge-chat__panel header {
padding: 12px 16px;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-bottom: 1px solid var(--color-border);
font-weight: bold;
}
.b01-knowledge-chat__avatar {
display: grid;
width: 34px;
height: 34px;
place-items: center;
border-radius: 50%;
background: var(--color-royal-amethyst, #6843a7);
color: #fff;
}
.b01-knowledge-chat__avatar svg {
width: 21px;
height: 21px;
}
.b01-knowledge-chat__close {
margin-left: auto;
border: 0;
background: transparent;
color: inherit;
font: inherit;
font-size: 26px;
cursor: pointer;
}
.b01-knowledge-chat__messages {
height: calc(100% - 106px);
padding: 12px;
display: flex;
flex: 1;
flex-direction: column;
gap: 12px;
min-height: 0;
padding: 16px 12px;
overflow-y: auto;
}
.b01-knowledge-chat__user,
.b01-knowledge-chat__reply {
margin: 0 0 12px;
padding: 10px;
border-radius: 8px;
max-width: 88%;
margin: 0;
padding: 10px 12px;
border-radius: 14px;
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.b01-knowledge-chat__user {
align-self: flex-end;
border-bottom-right-radius: 4px;
background: var(--color-royal-amethyst, #6843a7);
color: #fff;
}
.b01-knowledge-chat__reply {
align-self: flex-start;
border-bottom-left-radius: 4px;
background: var(--color-background, #f4f4f4);
}
.b01-knowledge-chat__reply details {
margin-top: 8px;
font-size: 0.9em;
}
.b01-knowledge-chat__reply summary {
cursor: pointer;
overflow-wrap: anywhere;
@@ -602,6 +658,7 @@
.b01-knowledge-chat__form {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-top: 1px solid var(--color-border);
@@ -615,3 +672,14 @@
border-radius: 6px;
font: inherit;
}
.b01-knowledge-chat__form button {
padding: 9px 14px;
border-radius: 8px;
}
.b01-knowledge-chat button:focus-visible,
.b01-knowledge-chat input:focus-visible {
outline: 2px solid var(--color-royal-amethyst, #6843a7);
outline-offset: 2px;
}
+2 -1
View File
@@ -49,9 +49,10 @@ def test_chat_searches_all_knowledge_markdown_except_excluded_folders(monkeypatc
result = asyncio.run(chat.ask_knowledge_chat(chat.ChatRequest(question="임도 기준"), {}))
assert result["answer"] == "설명 [1]"
paths = {source["path"] for source in result["sources"]}
assert paths == {"original/법률/현행.md", "technical_info/_검토.md"}
assert paths == {"original/법률/현행.md"}
assert "실무문서" not in sent["prompt"]
assert "비공개 사례" not in sent["prompt"]
assert "STmate" not in sent["prompt"]
assert "다산소프트" not in sent["prompt"]
assert "기술정보 안내" not in sent["prompt"]
chat._sections.cache_clear()