[Developer] #424 add MCP routing chatbot demo

This commit is contained in:
devmrko
2026-06-25 15:50:37 +09:00
parent cfad317d73
commit eac43439d5
7 changed files with 448 additions and 0 deletions

View File

@@ -503,6 +503,85 @@ body {
line-height: 1.45;
}
.chat-shell {
display: grid;
gap: 1rem;
}
.chat-thread {
display: grid;
gap: .75rem;
}
.chat-message {
border: 1px solid var(--rw-border);
border-radius: 8px;
max-width: 100%;
padding: .85rem;
}
.chat-message strong {
display: block;
font-size: .82rem;
margin-bottom: .45rem;
text-transform: uppercase;
}
.chat-message p {
margin: 0;
}
.user-message {
background: #edf2f7;
margin-left: auto;
width: min(760px, 92%);
}
.assistant-message {
background: var(--rw-surface-muted);
width: min(920px, 100%);
}
.chat-form {
border-top: 1px solid var(--rw-border);
display: grid;
gap: .75rem;
padding-top: 1rem;
}
.chat-form-row {
display: grid;
gap: .75rem;
grid-template-columns: minmax(180px, 1fr) minmax(120px, 180px);
}
.chat-form label {
color: var(--rw-muted);
font-size: .875rem;
font-weight: 700;
}
.chat-debug {
margin-top: .75rem;
}
.chat-debug summary {
color: var(--rw-primary);
cursor: pointer;
font-weight: 700;
}
@media (max-width: 720px) {
.chat-form-row {
grid-template-columns: 1fr;
}
.user-message,
.assistant-message {
width: 100%;
}
}
.probe-exchange {
border: 1px solid var(--rw-border);
border-radius: 8px;

View File

@@ -35,6 +35,7 @@
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button">MCP</button>
<div class="rw-menu-panel">
<a class="nav-link" href="/mcp-chatbot">Chatbot</a>
<a class="nav-link" href="/mcp-reasoning">Reasoning</a>
<a class="nav-link" href="/mcp-sse">SSE 서비스</a>
<a class="nav-link" href="/mcp-client-demo">Client Demo</a>

View File

@@ -0,0 +1,47 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="result">
<div class="chat-message user-message" th:if="${result}">
<strong>질문</strong>
<p th:text="${result.question()}">question</p>
</div>
<div class="chat-message assistant-message" th:if="${errorMessage}">
<strong>Router</strong>
<p th:text="${errorMessage}">error</p>
</div>
<div class="chat-message assistant-message" th:if="${result}">
<div class="section-heading">
<strong>Router</strong>
<span class="badge"
th:classappend="${result.status() == 'SUCCESS'} ? ' text-bg-success' : ' text-bg-secondary'"
th:text="${result.status()}">SUCCESS</span>
</div>
<div class="meta-row">
<span>Context: <code th:text="${result.contextPath()}">vpd-live</code></span>
<span>Tool: <code th:text="${result.selectedTool()}">tool</code></span>
</div>
<div class="markdown-view" th:text="${result.answer()}">answer</div>
<details class="chat-debug">
<summary>라우팅/도구 응답 보기</summary>
<div class="probe-exchange-grid">
<section class="probe-exchange">
<h3>Routing Reason</h3>
<pre th:text="${result.routingReason()}">reason</pre>
</section>
<section class="probe-exchange">
<h3>tools/list</h3>
<pre th:text="${result.toolsListJson()}">{}</pre>
</section>
<section class="probe-exchange">
<h3>tools/call</h3>
<pre th:text="${result.toolResultJson()}">{}</pre>
</section>
</div>
</details>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,49 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('MCP Chatbot')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container page-shell">
<section class="page-heading">
<h1>MCP Chatbot</h1>
<p>질문을 MCP tool로 라우팅하고 ORDS/VPD 조회 결과를 답변으로 정리합니다.</p>
</section>
<section class="content-band chat-shell">
<div class="chat-message assistant-message">
<strong>Router</strong>
<p>보호 객체명이나 업무명을 질문에 넣으면 가장 가까운 MCP tool을 선택합니다. Bearer Token을 입력하면 실제 ORDS 호출까지 실행합니다.</p>
</div>
<div id="mcp-chatbot-result" class="chat-thread">
<div class="text-muted">대화 결과가 여기에 표시됩니다.</div>
</div>
<form hx-post="/mcp-chatbot" hx-target="#mcp-chatbot-result" hx-swap="beforeend" class="chat-form">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<div class="chat-form-row">
<label>
Context
<input class="form-control" name="contextPath" value="vpd-live" pattern="[A-Za-z0-9][A-Za-z0-9_-]{0,63}" required>
</label>
<label>
Limit
<input class="form-control" name="limit" type="number" min="1" max="500" value="50">
</label>
</div>
<label>
Bearer Token 원문
<input class="form-control" name="bearerToken" type="password" autocomplete="off"
placeholder="비우면 라우팅만 확인합니다.">
</label>
<label>
질문
<textarea class="form-control" name="question" rows="3"
placeholder="예: BOARD_POSTS에서 이 토큰으로 보이는 행과 NULL 처리 컬럼을 요약해줘." required></textarea>
</label>
<button class="btn rw-btn-primary" type="submit">질문 보내기</button>
</form>
</section>
</main>
</body>
</html>