Update project functionality and configuration
This commit is contained in:
625
potal/agent-test-backend/bin/main/static/index.html
Normal file
625
potal/agent-test-backend/bin/main/static/index.html
Normal file
@@ -0,0 +1,625 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AX HUB Portal PoC</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4f6f9;
|
||||
--surface: #ffffff;
|
||||
--line: #d7dde8;
|
||||
--text: #172033;
|
||||
--muted: #667085;
|
||||
--primary: #1d5fd1;
|
||||
--primary-dark: #164aa5;
|
||||
--soft: #eef3fb;
|
||||
--good: #087443;
|
||||
--bad: #b42318;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: "Segoe UI", "Noto Sans KR", Arial, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
background: #162033;
|
||||
color: #fff;
|
||||
padding: 18px 28px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.header-meta {
|
||||
color: #cbd5e1;
|
||||
font-size: 13px;
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
main {
|
||||
width: min(1320px, calc(100% - 32px));
|
||||
margin: 18px auto 28px;
|
||||
display: grid;
|
||||
grid-template-columns: 360px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
section {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 17px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.stack {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.item {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.kv {
|
||||
display: grid;
|
||||
grid-template-columns: 110px minmax(0, 1fr);
|
||||
gap: 5px 10px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: #e7f6ee;
|
||||
color: var(--good);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin: 10px 0 6px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
font: inherit;
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 92px;
|
||||
font-family: Consolas, "Courier New", monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
min-height: 104px;
|
||||
font-family: "Segoe UI", "Noto Sans KR", Arial, sans-serif;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
min-height: 38px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: #344054;
|
||||
}
|
||||
|
||||
button.secondary:hover {
|
||||
background: #202939;
|
||||
}
|
||||
|
||||
button.soft {
|
||||
background: var(--soft);
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
button.soft:hover {
|
||||
background: #dfe8f6;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
min-height: 300px;
|
||||
padding: 14px;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
background: #101828;
|
||||
color: #e5e7eb;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.answer {
|
||||
margin-top: 12px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fbfcff;
|
||||
min-height: 80px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.manual-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
header,
|
||||
main,
|
||||
.two-col,
|
||||
.manual-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
header {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.header-meta {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div>
|
||||
<h1>AX HUB Portal PoC</h1>
|
||||
<div class="header-meta" style="text-align:left">Hardcoded Registry + Agent Backend + MCP Tool Call</div>
|
||||
</div>
|
||||
<div class="header-meta" id="config">Loading configuration...</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="stack">
|
||||
<section>
|
||||
<h2>Portal Registry</h2>
|
||||
<p class="header-meta" style="text-align:left;color:var(--muted)">필요할 때 MCP가 Registry를 다시 확인하도록 revision을 갱신합니다.</p>
|
||||
<input id="routeKey" value="cus" type="hidden">
|
||||
<div class="actions">
|
||||
<button onclick="bumpRevision()">Portal Revision 올리기</button>
|
||||
<button onclick="openNewBundle()">+ Tool Server 추가</button>
|
||||
</div>
|
||||
<details id="bundleEditor" style="margin-top:14px">
|
||||
<summary><strong>Tool Server 설정</strong></summary>
|
||||
<div style="margin-top:12px">
|
||||
<label for="bundleId">Bundle ID</label>
|
||||
<input id="bundleId" placeholder="business-tools">
|
||||
<label for="toolServerName">Tool Server 이름</label>
|
||||
<input id="toolServerName" placeholder="Business Tool Server">
|
||||
<label for="manifestUrl">Manifest URL</label>
|
||||
<input id="manifestUrl" placeholder="http://localhost:9090/tool-manifest">
|
||||
<label for="baseUrl">실행 Base URL</label>
|
||||
<input id="baseUrl" placeholder="http://localhost:9090">
|
||||
<label for="namePrefix">Tool name prefix</label>
|
||||
<input id="namePrefix" placeholder="business.">
|
||||
<label><input id="bundleEnabled" type="checkbox" checked style="width:auto"> 활성화</label>
|
||||
<details style="margin-top:12px">
|
||||
<summary>고급 설정</summary>
|
||||
<label for="pollInterval">Manifest 조회 주기(초)</label>
|
||||
<input id="pollInterval" type="number" min="5" value="60">
|
||||
<label for="apiKey">Tool Server API Key (입력 전용)</label>
|
||||
<input id="apiKey" type="password" autocomplete="new-password" placeholder="기존 Key를 유지하려면 비워두세요">
|
||||
<label for="toolEndpoints">Tool 실행 경로 매핑(JSON)</label>
|
||||
<textarea id="toolEndpoints">{}</textarea>
|
||||
</details>
|
||||
<div class="actions">
|
||||
<button onclick="saveBundle()">저장</button>
|
||||
<button class="soft" onclick="closeBundleEditor()">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<details>
|
||||
<summary><strong>MCP 연결 테스트</strong></summary>
|
||||
<label for="mcpToolServerFilter">확인할 Tool Server</label>
|
||||
<select id="mcpToolServerFilter" style="width:100%;padding:10px;border:1px solid var(--line);border-radius:6px">
|
||||
<option value="external.">External Tool Server</option>
|
||||
<option value="business.">Business Tool Server</option>
|
||||
<option value="cus">DAP WAS CUS Tool Server</option>
|
||||
</select>
|
||||
<div class="actions">
|
||||
<button onclick="callApi('POST', mcpTestAction('initialize'))">Initialize</button>
|
||||
<button class="secondary" onclick="callApi('POST', mcpTestAction('initialized'))">Initialized</button>
|
||||
<button onclick="loadMcpTools()">Tools/List</button>
|
||||
</div>
|
||||
<div id="mcpToolList" class="stack" style="margin-top:12px"></div>
|
||||
</details>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
<section>
|
||||
<details>
|
||||
<summary><strong>Agent 테스트</strong></summary>
|
||||
<label for="message">사용자 요청</label>
|
||||
<textarea id="message" class="chat-box">서울 날씨 알려줘</textarea>
|
||||
<div class="actions">
|
||||
<button onclick="sendChat()">Agent 실행</button>
|
||||
<button class="soft" onclick="setMessage('서울 날씨 알려줘')">서울 날씨</button>
|
||||
<button class="soft" onclick="setMessage('부산 날씨 알려줘')">부산 날씨</button>
|
||||
<button class="soft" onclick="setMessage('달러 환율 알려줘')">달러 환율</button>
|
||||
<button class="soft" onclick="setMessage('대한민국 공휴일 조회해줘')">공휴일 조회</button>
|
||||
<button class="soft" onclick="setMessage('서울 좌표 조회해줘')">서울 좌표</button>
|
||||
<button class="soft" onclick="setMessage('대한민국 국가 정보 조회해줘')">국가 정보</button>
|
||||
<button class="soft" onclick="setMessage('고객 조회해줘')">고객 조회</button>
|
||||
<button class="soft" onclick="setMessage('주문 상태 조회해줘')">주문 상태</button>
|
||||
<button class="soft" onclick="setMessage('고객 문의 티켓 생성해줘')">티켓 생성</button>
|
||||
<button class="soft" onclick="setMessage('메타 공통코드 조회해줘')">메타 공통코드</button>
|
||||
<button class="soft" onclick="setMessage('메타 테이블 조회해줘')">메타 테이블</button>
|
||||
<button class="soft" onclick="setMessage('템플릿 다운로드 URL 알려줘')">템플릿 URL</button>
|
||||
<button class="soft" onclick="setMessage('SOL 의뢰서 목록 조회해줘')">SOL 목록</button>
|
||||
<button class="soft" onclick="setMessage('SOL 의뢰서 상세 조회해줘')">SOL 상세</button>
|
||||
<button class="soft" onclick="setMessage('보험금 청구 처리해줘')">보험금 청구</button>
|
||||
<button class="soft" onclick="setMessage('가입설계 한도 조회해줘')">가입설계 한도</button>
|
||||
<button class="soft" onclick="setMessage('CUS 달러 환율 조회해줘')">CUS 환율</button>
|
||||
<button class="soft" onclick="setMessage('CUS 서울 날씨 조회해줘')">CUS 날씨</button>
|
||||
<button class="soft" onclick="setMessage('오늘의 명언 알려줘')">오늘의 명언</button>
|
||||
<button class="soft" onclick="setMessage('TOOL 파트 구성원 조회해줘')">TOOL 구성원</button>
|
||||
</div>
|
||||
<div class="answer" id="answer">Agent 응답 대기 중</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<details>
|
||||
<summary><strong>Tool 직접 호출</strong></summary>
|
||||
<label for="toolPreset">호출할 Tool</label>
|
||||
<select id="toolPreset" onchange="setToolPreset(this.value)" style="width:100%;padding:10px;border:1px solid var(--line);border-radius:6px">
|
||||
<option value="external.weather_lookup">날씨 조회</option>
|
||||
<option value="external.exchange_rate">환율 조회</option>
|
||||
<option value="external.public_holiday_lookup">공휴일 조회</option>
|
||||
<option value="external.geocoding_lookup">도시 좌표 조회</option>
|
||||
<option value="external.country_info_lookup">국가 정보 조회</option>
|
||||
<option value="business.customer_search">고객 검색</option>
|
||||
<option value="business.order_status">주문 상태 조회</option>
|
||||
<option value="business.ticket_create">지원 티켓 생성(승인 필요)</option>
|
||||
<option value="cmm_comcode_lookup">메타 공통코드 조회</option>
|
||||
<option value="cmm_customer_tool">고객 통합 안내이력 조회</option>
|
||||
<option value="cmm_meta_table">메타 테이블 조회</option>
|
||||
<option value="cmm_template_url">템플릿 URL 조회</option>
|
||||
<option value="sol_request_list">SOL 의뢰서 목록</option>
|
||||
<option value="sol_request_detail">SOL 의뢰서 상세</option>
|
||||
<option value="ins_insurance_processor">보험금 청구 처리</option>
|
||||
<option value="oth_onnba3011_call">가입설계 한도 조회</option>
|
||||
<option value="smp_exchange_inquiry">CUS 환율 조회</option>
|
||||
<option value="smp_weather_inquiry">CUS 날씨 조회</option>
|
||||
<option value="smp_quote_daily">오늘의 명언</option>
|
||||
<option value="smp_team_list">TOOL 파트 구성원</option>
|
||||
</select>
|
||||
<div class="manual-grid">
|
||||
<div>
|
||||
<label for="toolName">Tool Name</label>
|
||||
<input id="toolName" value="external.weather_lookup">
|
||||
</div>
|
||||
<div>
|
||||
<label for="arguments">Arguments JSON</label>
|
||||
<textarea id="arguments">{
|
||||
"city": "Seoul",
|
||||
"timezone": "Asia/Seoul"
|
||||
}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button onclick="callTool()">Tools/Call</button>
|
||||
<button class="soft" onclick="setWeather()">Weather Args</button>
|
||||
<button class="soft" onclick="setExchange()">Exchange Args</button>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<details>
|
||||
<summary><strong>상세 응답 보기</strong></summary>
|
||||
<pre id="output">Waiting...</pre>
|
||||
</details>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const output = document.getElementById("output");
|
||||
const config = document.getElementById("config");
|
||||
const answer = document.getElementById("answer");
|
||||
|
||||
async function callApi(method, url, body) {
|
||||
output.textContent = "Requesting...";
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
const text = await response.text();
|
||||
const data = text ? JSON.parse(text) : {};
|
||||
output.textContent = JSON.stringify(data, null, 2);
|
||||
if (!response.ok) {
|
||||
throw new Error(data.detail || data.message || data.error || `HTTP ${response.status}`);
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
output.textContent = JSON.stringify({error: error.message}, null, 2);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
const data = await callApi("GET", "/api/config");
|
||||
document.getElementById("routeKey").value = data.defaultRouteKey || "cus";
|
||||
config.innerHTML = `MCP ${data.mcpEndpointUrl}<br>Route MCP ${data.defaultRoutedMcpEndpointUrl}<br>Manifest ${data.toolManifestUrl}`;
|
||||
}
|
||||
|
||||
async function loadRegistry() {
|
||||
const bundles = await callApi("GET", "/api/portal/bundles");
|
||||
window.portalBundles = bundles;
|
||||
}
|
||||
|
||||
function openNewBundle() {
|
||||
clearBundleForm();
|
||||
document.getElementById("bundleEditor").open = true;
|
||||
}
|
||||
|
||||
function closeBundleEditor() {
|
||||
document.getElementById("bundleEditor").open = false;
|
||||
}
|
||||
|
||||
function clearBundleForm() {
|
||||
["bundleId", "toolServerName", "manifestUrl", "baseUrl", "namePrefix", "apiKey"]
|
||||
.forEach(id => document.getElementById(id).value = "");
|
||||
document.getElementById("bundleId").readOnly = false;
|
||||
document.getElementById("pollInterval").value = 60;
|
||||
document.getElementById("toolEndpoints").value = "{}";
|
||||
document.getElementById("bundleEnabled").checked = true;
|
||||
}
|
||||
|
||||
async function saveBundle() {
|
||||
const idInput = document.getElementById("bundleId");
|
||||
const payload = {
|
||||
bundleId: idInput.value.trim(),
|
||||
toolServerName: document.getElementById("toolServerName").value.trim(),
|
||||
manifestUrl: document.getElementById("manifestUrl").value.trim(),
|
||||
baseUrl: document.getElementById("baseUrl").value.trim(),
|
||||
namePrefix: document.getElementById("namePrefix").value.trim(),
|
||||
enabled: document.getElementById("bundleEnabled").checked,
|
||||
manifestPollIntervalSeconds: Number(document.getElementById("pollInterval").value),
|
||||
apiKey: document.getElementById("apiKey").value,
|
||||
toolEndpoints: JSON.parse(document.getElementById("toolEndpoints").value || "{}")
|
||||
};
|
||||
const url = idInput.readOnly
|
||||
? `/api/portal/bundles/${encodeURIComponent(payload.bundleId)}`
|
||||
: "/api/portal/bundles";
|
||||
await callApi(idInput.readOnly ? "PUT" : "POST", url, payload);
|
||||
await loadRegistry();
|
||||
closeBundleEditor();
|
||||
}
|
||||
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value).replace(/[&<>"']/g, char => ({"&":"&","<":"<",">":">","\"":""","'":"'"})[char]);
|
||||
}
|
||||
|
||||
function escapeJs(value) {
|
||||
return String(value).replace(/[\\']/g, "\\$&");
|
||||
}
|
||||
|
||||
async function bumpRevision() {
|
||||
const data = await callApi("POST", `/api/portal/registry/${selectedRoute()}/revision`);
|
||||
await loadRegistry();
|
||||
output.textContent = JSON.stringify({
|
||||
message: "Portal registry revision bumped",
|
||||
routeKey: data.routeKey,
|
||||
registryRevision: data.registryRevision
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
function renderGroup(title, rows) {
|
||||
return `<div><h3>${title}</h3>${rows.map(renderItem).join("")}</div>`;
|
||||
}
|
||||
|
||||
function renderItem(row) {
|
||||
const title = row.displayName || row.serviceKey || row.routeKey;
|
||||
const entries = Object.entries(row)
|
||||
.map(([key, value]) => `<div>${key}</div><div>${value}</div>`)
|
||||
.join("");
|
||||
return `<div class="item"><div class="item-title">${title} <span class="badge">${row.status || row.source}</span></div><div class="kv">${entries}</div></div>`;
|
||||
}
|
||||
|
||||
async function sendChat() {
|
||||
const message = document.getElementById("message").value.trim();
|
||||
answer.textContent = "Agent is selecting a tool...";
|
||||
const data = await callApi("POST", "/api/agent/chat", {message});
|
||||
answer.innerHTML = `<strong>${data.answer}</strong><br><br>Agent route 결정: ${data.routeKey}<br>판단: ${data.routeDecision}<br>MCP: ${data.mcpEndpointUrl}<br>Tool: ${data.selectedTool}<br>인자: ${JSON.stringify(data.arguments)}`;
|
||||
}
|
||||
|
||||
async function callTool() {
|
||||
const name = document.getElementById("toolName").value.trim();
|
||||
const args = JSON.parse(document.getElementById("arguments").value);
|
||||
const routeKey = name.startsWith("business.") ? "business"
|
||||
: /^(cmm|ins|oth|smp|sol)_/.test(name) ? "cus" : "external";
|
||||
await callApi("POST", `/api/mcp/${routeKey}/tools/call`, {name, arguments: args});
|
||||
}
|
||||
|
||||
function setToolPreset(name) {
|
||||
const presets = {
|
||||
"external.weather_lookup": {city: "Seoul", timezone: "Asia/Seoul"},
|
||||
"external.exchange_rate": {from: "USD", to: "KRW"},
|
||||
"external.public_holiday_lookup": {countryCode: "KR", year: new Date().getFullYear()},
|
||||
"external.geocoding_lookup": {city: "Seoul", language: "ko"},
|
||||
"external.country_info_lookup": {countryCode: "KR"},
|
||||
"business.customer_search": {keyword: "C-1001"},
|
||||
"business.order_status": {orderId: "O-9001"},
|
||||
"business.ticket_create": {
|
||||
title: "Portal test ticket",
|
||||
priority: "normal",
|
||||
description: "Created from the Portal Tool test screen"
|
||||
},
|
||||
"cmm_comcode_lookup": {groupCode: "GRP_COMM_CD", useYn: "Y"},
|
||||
"cmm_customer_tool": {csNo: "000000000001"},
|
||||
"cmm_meta_table": {tableName: "TB_CUST_BAS", owner: "DAPADM"},
|
||||
"cmm_template_url": {templateId: "TPL_001"},
|
||||
"sol_request_list": {status: "진행중", period: "1개월", target: "나의 업무"},
|
||||
"sol_request_detail": {srId: "SR-001"},
|
||||
"ins_insurance_processor": {claimNumber: "CLM20230001", claimAmount: 1500000, claimDate: "2026-08-12"},
|
||||
"oth_onnba3011_call": {dalScCd: "1", cstSucoRltyCd: "01", csNo: "000000000001"},
|
||||
"smp_exchange_inquiry": {currencyCode: "USD"},
|
||||
"smp_weather_inquiry": {city: "서울"},
|
||||
"smp_quote_daily": {category: "속담"},
|
||||
"smp_team_list": {teamName: "TOOL"}
|
||||
};
|
||||
document.getElementById("toolName").value = name;
|
||||
document.getElementById("arguments").value = JSON.stringify(presets[name] || {}, null, 2);
|
||||
}
|
||||
|
||||
function selectedRoute() {
|
||||
const route = document.getElementById("routeKey").value.trim();
|
||||
return route || "cus";
|
||||
}
|
||||
|
||||
function mcpAction(action) {
|
||||
return `/api/mcp/${selectedRoute()}/${action}`;
|
||||
}
|
||||
|
||||
async function loadMcpTools() {
|
||||
const data = await callApi("POST", mcpTestAction("tools/list"));
|
||||
const prefix = document.getElementById("mcpToolServerFilter").value;
|
||||
const tools = (((data || {}).body || {}).result || {}).tools || [];
|
||||
const filtered = prefix === "all" || prefix === "cus"
|
||||
? tools : tools.filter(tool => tool.name.startsWith(prefix));
|
||||
const target = document.getElementById("mcpToolList");
|
||||
target.innerHTML = filtered.length ? filtered.map(tool => `
|
||||
<div class="item">
|
||||
<div class="item-title">${escapeHtml(tool.title || tool.name)}</div>
|
||||
<div class="kv">
|
||||
<div>name</div><div>${escapeHtml(tool.name)}</div>
|
||||
<div>설명</div><div>${escapeHtml(tool.description || "-")}</div>
|
||||
<div>유형</div><div>${tool.annotations && tool.annotations.readOnlyHint ? "READ" : "WRITE"}</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="soft" onclick="prepareToolCall('${escapeJs(tool.name)}')">이 Tool 호출</button>
|
||||
</div>
|
||||
</div>`).join("") : `<div class="header-meta" style="text-align:left;color:var(--muted)">선택한 서버의 Tool이 없습니다.</div>`;
|
||||
}
|
||||
|
||||
function mcpTestAction(action) {
|
||||
const filter = document.getElementById("mcpToolServerFilter").value;
|
||||
const routeKey = filter === "business." ? "business"
|
||||
: filter === "cus" ? "cus" : "external";
|
||||
return `/api/mcp/${routeKey}/${action}`;
|
||||
}
|
||||
|
||||
function prepareToolCall(name) {
|
||||
setToolPreset(name);
|
||||
document.getElementById("toolPreset").value = name;
|
||||
document.getElementById("toolPreset").closest("details").open = true;
|
||||
document.getElementById("toolPreset").scrollIntoView({behavior: "smooth", block: "center"});
|
||||
}
|
||||
|
||||
function setMessage(value) {
|
||||
document.getElementById("message").value = value;
|
||||
}
|
||||
|
||||
function setWeather() {
|
||||
document.getElementById("toolName").value = "external.weather_lookup";
|
||||
document.getElementById("arguments").value = JSON.stringify({
|
||||
city: "Seoul",
|
||||
timezone: "Asia/Seoul"
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
function setExchange() {
|
||||
document.getElementById("toolName").value = "external.exchange_rate";
|
||||
document.getElementById("arguments").value = JSON.stringify({
|
||||
from: "USD",
|
||||
to: "KRW"
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
loadConfig().then(loadRegistry).catch(error => {
|
||||
config.textContent = "Configuration load failed";
|
||||
output.textContent = JSON.stringify({error: error.message}, null, 2);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user