[Developer] #424 add MCP reasoning tab
This commit is contained in:
@@ -30,4 +30,10 @@ backoffice:
|
||||
max-days: ${BACKOFFICE_TOKEN_MAX_DAYS:365}
|
||||
ords:
|
||||
base-url: ${BACKOFFICE_ORDS_BASE_URL:https://yh0olybn5pqce4n-d8aukro81636mon0.adb.ap-seoul-1.oraclecloudapps.com/ords}
|
||||
timeout-seconds: ${BACKOFFICE_ORDS_TIMEOUT_SECONDS:10}
|
||||
timeout: ${BACKOFFICE_ORDS_TIMEOUT_SECONDS:10}s
|
||||
ai:
|
||||
enabled: ${BACKOFFICE_AI_ENABLED:false}
|
||||
base-url: ${BACKOFFICE_AI_BASE_URL:}
|
||||
model: ${BACKOFFICE_AI_MODEL:}
|
||||
api-key: ${BACKOFFICE_AI_API_KEY:}
|
||||
timeout: ${BACKOFFICE_AI_TIMEOUT_SECONDS:30}s
|
||||
|
||||
@@ -392,6 +392,31 @@ body {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.ai-answer {
|
||||
background: var(--rw-primary-soft);
|
||||
border: 1px solid var(--rw-border);
|
||||
border-radius: 8px;
|
||||
margin: 1rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ai-answer h3 {
|
||||
border-bottom: 1px solid var(--rw-border);
|
||||
font-size: .9rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
padding: .6rem .8rem;
|
||||
}
|
||||
|
||||
.ai-answer pre {
|
||||
color: var(--rw-text);
|
||||
font-family: inherit;
|
||||
font-size: .95rem;
|
||||
margin: 0;
|
||||
padding: .85rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.form-grid .span-2 {
|
||||
grid-column: span 1;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<a class="nav-link" href="/objects">테이블/뷰</a>
|
||||
<a class="nav-link" href="/tokens">토큰</a>
|
||||
<a class="nav-link" href="/probe">ORDS 검증</a>
|
||||
<a class="nav-link" href="/mcp-reasoning">MCP Reasoning</a>
|
||||
<a class="nav-link" href="/ords-handlers">ORDS 핸들러</a>
|
||||
<a class="nav-link" href="/settings">설정</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
<div th:fragment="result">
|
||||
<div class="section-heading">
|
||||
<h2>Reasoning 결과</h2>
|
||||
<span class="badge"
|
||||
th:classappend="${result.modelStatus() == 'SUCCESS'} ? ' text-bg-success' : ' text-bg-warning'"
|
||||
th:text="${result.modelStatus()}">SUCCESS</span>
|
||||
</div>
|
||||
|
||||
<div class="meta-row">
|
||||
<span>Tool: <code th:text="${result.toolName()}">tool</code></span>
|
||||
<span>ORDS Status: <strong th:text="${result.probeResult().status()}">SUCCESS</strong></span>
|
||||
<span>Rows: <strong th:text="${result.probeResult().rowCount()}">0</strong></span>
|
||||
</div>
|
||||
|
||||
<section class="ai-answer">
|
||||
<h3>Answer</h3>
|
||||
<pre th:text="${result.answer()}">answer</pre>
|
||||
</section>
|
||||
|
||||
<div class="probe-exchange-grid">
|
||||
<section class="probe-exchange">
|
||||
<h3>Prompt</h3>
|
||||
<pre th:text="${result.prompt()}">prompt</pre>
|
||||
</section>
|
||||
<section class="probe-exchange">
|
||||
<h3>Tool Evidence JSON</h3>
|
||||
<pre th:text="${result.evidenceJson()}">{}</pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="probe-exchange-grid">
|
||||
<section class="probe-exchange">
|
||||
<h3>Request Headers</h3>
|
||||
<pre th:text="${result.probeResult().requestHeaders()} ?: '{}'">{}</pre>
|
||||
</section>
|
||||
<section class="probe-exchange">
|
||||
<h3>Response Body</h3>
|
||||
<pre th:text="${result.probeResult().responseBody()} ?: ''"></pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive" th:if="${!#lists.isEmpty(result.probeResult().rows())}">
|
||||
<table class="table table-sm table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:each="column : ${result.probeResult().columns()}" th:text="${column}">column</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="row : ${result.probeResult().rows()}">
|
||||
<td th:each="column : ${result.probeResult().columns()}" th:text="${row.get(column)}">value</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
85
src/main/resources/templates/mcp-reasoning.html
Normal file
85
src/main/resources/templates/mcp-reasoning.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('MCP Reasoning')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>MCP Reasoning</h1>
|
||||
<p>보호 객체 ORDS 호출을 내부 도구로 실행하고, 실행 증거를 OpenAI 호환 모델에 전달해 권한 결과를 해석합니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" th:if="${runtimeError}">
|
||||
<strong th:text="${runtimeError.title()}">DB 연결 설정이 필요합니다.</strong>
|
||||
<span th:text="${runtimeError.message()}">message</span>
|
||||
<div th:if="${runtimeError.showSupportCommand()}">
|
||||
<code>./run.sh backoffice-support</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<h2>도구 실행</h2>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/mcp/tools" target="_blank" rel="noreferrer">도구 JSON</a>
|
||||
</div>
|
||||
<form hx-post="/mcp-reasoning" hx-target="#mcp-result" hx-swap="innerHTML" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
Bearer Token 원문
|
||||
<input class="form-control" name="bearerToken" type="password" autocomplete="off" required>
|
||||
</label>
|
||||
<label>
|
||||
도구
|
||||
<select class="form-select" name="objectId" required>
|
||||
<option th:each="tool : ${tools}"
|
||||
th:value="${tool.objectId()}"
|
||||
th:text="${tool.name() + ' / ' + tool.displayName()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Limit
|
||||
<input class="form-control" name="limit" type="number" min="1" max="500" value="50">
|
||||
</label>
|
||||
<label class="span-2">
|
||||
질문
|
||||
<textarea class="form-control" name="question" rows="3"
|
||||
placeholder="예: 이 토큰으로 어떤 행이 보이고 어떤 컬럼이 NULL 처리됐는지 요약해줘."></textarea>
|
||||
</label>
|
||||
<button class="btn rw-btn-primary" type="submit">Reasoning 실행</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<h2>등록된 도구</h2>
|
||||
<span class="badge text-bg-secondary" th:text="${#lists.size(tools)}">0</span>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Object</th>
|
||||
<th>ORDS Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="tool : ${tools}">
|
||||
<td><code th:text="${tool.name()}">tool</code></td>
|
||||
<td th:text="${tool.displayName()}">ADMIN.TABLE</td>
|
||||
<td><code th:text="${tool.ordsPath()}">path</code></td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(tools)}">
|
||||
<td colspan="3" class="text-muted">등록된 보호 객체 도구가 없습니다.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mcp-result" class="content-band">
|
||||
<div class="text-muted">Reasoning 결과가 여기에 표시됩니다.</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user