fix: restore corrupted UTF-8 encoding in HTML files
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 11s

This commit is contained in:
jade
2026-08-27 14:57:03 +09:00
parent 23cafbf8e2
commit 69ac0ab1df
2 changed files with 145 additions and 166 deletions

View File

@@ -805,13 +805,13 @@
<div class="col-md-12">
<label class="form-label">Workspace Path</label>
<div class="input-group">
<input type="text" class="form-control" name="workspacePath" id="podWorkspacePathInput" placeholder="ì§<EFBFBD>ì  ?´ë<C2B4>” 경로ë¥??…ë ¥(붙여?£ê¸°) ?˜ê±°??? íƒ<C3AD>?˜ì„¸?? value="c:\eGovFrameDev-4.3.1-64bit\workspace-egov\dat-was-datmt">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" style="border-color: #3f3f46; color: #a1a1aa;">?´ë<C2B4>” ? íƒ<C3AD></button>
<input type="text" class="form-control" name="workspacePath" id="podWorkspacePathInput" placeholder="직접 폴더 경로를 입력(붙여넣기) 하거나 선택하세요" value="c:\eGovFrameDev-4.3.1-64bit\workspace-egov\dat-was-datmt">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" style="border-color: #3f3f46; color: #a1a1aa;">폴더 선택</button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-dark" style="background-color: #18181b; border-color: #3f3f46;">
<li><a class="dropdown-item" href="#" onclick="const input = document.getElementById('podWorkspacePathInput'); input.value='c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dat-was-datmt'; input.dispatchEvent(new Event('change')); return false;" style="color: #f4f4f5;">dat-was-datmt (기본)</a></li>
<li><a class="dropdown-item" href="#" onclick="const input = document.getElementById('podWorkspacePathInput'); input.value='c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\axhub-backend-main'; input.dispatchEvent(new Event('change')); return false;" style="color: #f4f4f5;">axhub-backend-main</a></li>
<li><hr class="dropdown-divider" style="border-color: #3f3f46;"></li>
<li><a class="dropdown-item" href="#" onclick="openServerFolderPicker('podWorkspacePathInput'); return false;" style="color: #f4f4f5;">?´ë<C2B4>” ? íƒ<C3AD>(+)</a></li>
<li><a class="dropdown-item" href="#" onclick="openServerFolderPicker('podWorkspacePathInput'); return false;" style="color: #f4f4f5;">폴더 선택(+)</a></li>
</ul>
</div>
<input type="file" id="podFolderPicker" webkitdirectory directory style="display: none;" onchange="
@@ -827,7 +827,7 @@
input.dispatchEvent(new Event('change'));
}
">
<div class="input-hint">ì§<EFBFBD>ì  ê²½ë¡œë¥?붙여?£ê±°???°ì¸¡ <b>[?´ë<C2B4>” ? íƒ<C3AD>]</b> 버튼???ŒëŸ¬ ? íƒ<C3AD>?˜ì„¸??</div>
<div class="input-hint">직접 경로를 붙여넣거나 우측 <b>[폴더 선택]</b> 버튼을 눌러 선택하세요.</div>
</div>
</div>
<div class="mb-4">
@@ -862,13 +862,13 @@
<div class="col-md-12">
<label class="form-label">Workspace Path</label>
<div class="input-group">
<input type="text" class="form-control" name="workspacePath" id="workspacePathInput" placeholder="ì§<EFBFBD>ì  ?´ë<C2B4>” 경로ë¥??…ë ¥(붙여?£ê¸°) ?˜ê±°??? íƒ<C3AD>?˜ì„¸?? value="c:\eGovFrameDev-4.3.1-64bit\workspace-egov\dat-was-datmt">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" style="border-color: #3f3f46; color: #a1a1aa;">?´ë<C2B4>” ? íƒ<C3AD></button>
<input type="text" class="form-control" name="workspacePath" id="workspacePathInput" placeholder="직접 폴더 경로를 입력(붙여넣기) 하거나 선택하세요" value="c:\eGovFrameDev-4.3.1-64bit\workspace-egov\dat-was-datmt">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" style="border-color: #3f3f46; color: #a1a1aa;">폴더 선택</button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-dark" style="background-color: #18181b; border-color: #3f3f46;">
<li><a class="dropdown-item" href="#" onclick="const input = document.getElementById('workspacePathInput'); input.value='c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dat-was-datmt'; input.dispatchEvent(new Event('change')); return false;" style="color: #f4f4f5;">dat-was-datmt (기본)</a></li>
<li><a class="dropdown-item" href="#" onclick="const input = document.getElementById('workspacePathInput'); input.value='c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\axhub-backend-main'; input.dispatchEvent(new Event('change')); return false;" style="color: #f4f4f5;">axhub-backend-main</a></li>
<li><hr class="dropdown-divider" style="border-color: #3f3f46;"></li>
<li><a class="dropdown-item" href="#" onclick="openServerFolderPicker('workspacePathInput'); return false;" style="color: #f4f4f5;">?´ë<C2B4>” ? íƒ<C3AD>(+)</a></li>
<li><a class="dropdown-item" href="#" onclick="openServerFolderPicker('workspacePathInput'); return false;" style="color: #f4f4f5;">폴더 선택(+)</a></li>
</ul>
</div>
<input type="file" id="toolFolderPicker" webkitdirectory directory style="display: none;" onchange="
@@ -884,7 +884,7 @@
input.dispatchEvent(new Event('change'));
}
">
<div class="input-hint">ì§<EFBFBD>ì  ê²½ë¡œë¥?붙여?£ê±°???°ì¸¡ <b>[?´ë<C2B4>” ? íƒ<C3AD>]</b> 버튼???ŒëŸ¬ ? íƒ<C3AD>?˜ì„¸??</div>
<div class="input-hint">직접 경로를 붙여넣거나 우측 <b>[폴더 선택]</b> 버튼을 눌러 선택하세요.</div>
</div>
</div>
<div class="row mb-4">
@@ -915,11 +915,11 @@
<div class="row g-2">
<div class="col-md-5">
<select id="domainCategorySelect" class="form-select">
<option value="">???„ë©”???<3F>성</option>
<option value="">새 도메인 생성</option>
</select>
</div>
<div class="col-md-7">
<input type="text" id="domainCategoryName" class="form-control" name="categoryKey" pattern="^[a-z0-9]{3}$" maxlength="3" minlength="3" placeholder="e.g. cmm (3글???<3F>소문ìž<C3AC>)" oninput="this.value = this.value.toLowerCase()" required>
<input type="text" id="domainCategoryName" class="form-control" name="categoryKey" pattern="^[a-z0-9]{3}$" maxlength="3" minlength="3" placeholder="e.g. cmm (3글자 영소문자)" oninput="this.value = this.value.toLowerCase()" required>
</div>
</div>
</div>
@@ -935,38 +935,36 @@
<div class="d-flex justify-content-between align-items-center gap-3">
<div>
<label class="form-label mb-1">Multi Tool UseCase (HTTP / MCI)</label>
<div class="input-hint mt-0">?„재 Tool??ê°™ì? UseCase??ì¶”ê??˜ë©´, Toolë³?Clientë¥??¸ì¶œ?˜ëŠ” ?¬ëŸ¬ MCP Tool 메서?œê? ?<3F>성?©ë‹ˆ??</div>
<div class="input-hint mt-0">현재 Tool을 같은 UseCase에 추가하면, ToolClient를 호출하는 여러 MCP Tool 메서드가 생성됩니다.</div>
</div>
<button type="button" class="btn-secondary-action" onclick="addCurrentToolToGroup()">?„재 Tool 묶ì<EFBFBD>Œ??ì¶”ê?</button>
<button type="button" class="btn-secondary-action" onclick="addCurrentToolToGroup()">현재 Tool 묶음에 추가</button>
</div>
<div class="row g-2 mt-1">
<div class="col-md-4"><select id="toolGroupUseCaseSelect" class="form-select"><option value="">??UseCase ?<3F>성</option></select></div>
<div class="col-md-5"><input id="toolGroupUseCaseName" type="text" class="form-control" placeholder="UseCase ?´ë¦„ e.g. Customer"></div>
<div class="col-md-7"><div id="toolGroupSummary" class="input-hint pt-2">묶ì<EFBFBD>Œ??ì¶”ê???Tool ?†ì<E280A0>Œ ???¼ë°˜ Generate Tool?€ 기존 ?¨ì<C2A8>¼ Tool ?<3F>성?…니??</div></div>
<div class="col-md-4"><select id="toolGroupUseCaseSelect" class="form-select"><option value="">UseCase 생성</option></select></div>
<div class="col-md-5"><input id="toolGroupUseCaseName" type="text" class="form-control" placeholder="UseCase 이름 e.g. Customer"></div>
<div class="col-md-7"><div id="toolGroupSummary" class="input-hint pt-2">묶음에 추가된 Tool 없음 — 일반 Generate Tool은 기존 단일 Tool 생성입니다.</div></div>
</div>
</div>
<div class="mb-4 p-3 rounded" style="background:#18181b; border:1px solid #3f3f46;">
<label class="form-label">AI Tool 초안 만들�/label>
<label class="form-label">AI Tool 초안 만들기</label>
<div class="row g-2">
<div class="col-md-6">
<input id="toolNaturalLanguage" type="text" class="form-control" placeholder="?? ê³ ê°<C3AA>번호ë¡?계약 ?<3F>태ë¥?조회?˜ëŠ” Tool">
<input id="toolNaturalLanguage" type="text" class="form-control" placeholder="예: 고객번호로 계약 상태를 조회하는 Tool">
</div>
<div class="col-md-3">
<select id="aiModelSelect" class="form-select" aria-label="AI 모ë<EFBFBD>¸ ? íƒ<C3AD>">
<option value="Gemma-4-31B" selected>Gemma-4-31B (?¬ë‚´ 로컬 ?œê³µ)</option>
<option value="Qwen3-Coder">Qwen3-Coder (?¬ë‚´ 로컬 ?œê³µ)</option>
<option value="cohere/north-mini-code:free">Cohere North Mini Code (?¸ë?)</option>
<option value="inclusionai/ling-3.0-flash:free">InclusionAI Ling 3 Flash (?¸ë?)</option>
<option value="openai/gpt-oss-20b:free">OpenAI GPT-OSS 20B (?¸ë?)</option>
<option value="google/gemma-4-31b-it:free">Google Gemma 4 31B (?¸ë?)</option>
<option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron 3 Nano (?¸ë?)</option>
<select id="aiModelSelect" class="form-select" aria-label="AI 모델 선택">
<option value="cohere/north-mini-code:free" selected>Cohere North Mini Code</option>
<option value="inclusionai/ling-3.0-flash:free">InclusionAI Ling 3 Flash</option>
<option value="openai/gpt-oss-20b:free">OpenAI GPT-OSS 20B</option>
<option value="google/gemma-4-31b-it:free">Google Gemma 4 31B</option>
<option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron 3 Nano</option>
</select>
</div>
<div class="col-md-3 d-grid">
<button id="toolDraftButton" type="button" class="btn-action" onclick="createAiToolDraft()">AI�Tool 채우�/button>
<button id="toolDraftButton" type="button" class="btn-action" onclick="createAiToolDraft()">AITool 채우기</button>
</div>
</div>
<div class="input-hint">? íƒ<C3AD>??AIê°€ Tool 기본 ?•ë³´?€ Input/Output Fieldsë¥?채ì<C3AC>?ˆë‹¤. Legacy Interface ID?€ Client System Code???¤ì œ ?°ê³„ 명세ë¥??•ì<E280A2>¸??ì§<C3AC>ì  ?…ë ¥?˜ì„¸??</div>
<div class="input-hint">선택한 AI가 Tool 기본 정보와 Input/Output Fields를 채웁니다. Legacy Interface ID Client System Code는 실제 연계 명세를 확인해 직접 입력하세요.</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
@@ -994,31 +992,31 @@
<div class="mb-3 p-3 rounded" style="background:#18181b; border:1px solid #3f3f46;">
<div class="form-label mb-2">Tool Schema V17 Metadata</div>
<div class="input-hint mb-3">LLM??Tool???¬ë°”르게 ? íƒ<C3AD>?˜ë<CB9C>„ë¡?기능·?¬ìš© 조건·?œì™¸ 조건·?…ì¶œ???œí•œ??분리???…ë ¥?©ë‹ˆ??</div>
<div class="input-hint mb-3">LLMTool을 올바르게 선택하도록 기능·사용 조건·제외 조건·입출력 제한을 분리해 입력합니다.</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Function Description</label>
<textarea class="form-control" name="functionDescription" rows="2" placeholder="??Tool???˜í–‰?˜ëŠ” ?µì‹¬ 기능"></textarea>
<textarea class="form-control" name="functionDescription" rows="2" placeholder="Tool이 수행하는 핵심 기능"></textarea>
</div>
<div class="col-md-6">
<label class="form-label">Display Description</label>
<textarea class="form-control" name="displayDescription" rows="2" placeholder="Portal??ë³´ì—¬ì¤?ì§§ì? ?¤ëª…"></textarea>
<textarea class="form-control" name="displayDescription" rows="2" placeholder="Portal에 보여줄 짧은 설명"></textarea>
</div>
<div class="col-md-6">
<label class="form-label">When To Use</label>
<textarea class="form-control" name="whenToUse" rows="2" placeholder="?´ë–¤ ?¬ìš©???”ì²­?<3F>서 ??Tool???¬ìš©?˜ëŠ”ì§€"></textarea>
<textarea class="form-control" name="whenToUse" rows="2" placeholder="어떤 사용자 요청에서 이 Tool을 사용하는지"></textarea>
</div>
<div class="col-md-6">
<label class="form-label">When Not To Use</label>
<textarea class="form-control" name="whenNotToUse" rows="2" placeholder="??Tool???¬ìš©?˜ë©´ ???˜ëŠ” ì¡°ê±´"></textarea>
<textarea class="form-control" name="whenNotToUse" rows="2" placeholder="Tool을 사용하면 안 되는 조건"></textarea>
</div>
<div class="col-md-12">
<label class="form-label">I/O Limits</label>
<textarea class="form-control" name="ioLimits" rows="2" placeholder="?ˆìš©?˜ëŠ” ?…ë ¥, 반환 범위, 건수 ?œí•œ ??></textarea>
<textarea class="form-control" name="ioLimits" rows="2" placeholder="허용되는 입력, 반환 범위, 건수 제한 등"></textarea>
</div>
<div class="col-md-8">
<label class="form-label">Example Queries (3~10)</label>
<textarea class="form-control" name="exampleQueries" rows="3" placeholder="??줄ì—<C3AC> ?˜ë‚˜???…ë ¥\n?? ?¬ë²ˆ 10001 ì§<C3AC>ì<C3AC> ?•ë³´ë¥?조회?´ì¤˜"></textarea>
<textarea class="form-control" name="exampleQueries" rows="3" placeholder="한 줄에 하나씩 입력\n예: 사번 10001 직원 정보를 조회해줘"></textarea>
</div>
<div class="col-md-4">
<label class="form-label">Tags</label>
@@ -1046,8 +1044,8 @@
<div class="d-flex justify-content-between align-items-center mb-1">
<label class="form-label mb-0">Input Fields (JSON)</label>
<div class="d-flex gap-1">
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('inputFields')">?„드 ?¸ì§‘</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('inputFields')">?ˆì œ ?£ê¸°</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('inputFields')">필드 편집</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('inputFields')">예제 넣기</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('inputFields')">복사</button>
</div>
</div>
@@ -1057,8 +1055,8 @@
<div class="d-flex justify-content-between align-items-center mb-1">
<label class="form-label mb-0">Output Fields (JSON)</label>
<div class="d-flex gap-1">
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('outputFields')">?„드 ?¸ì§‘</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('outputFields')">?ˆì œ ?£ê¸°</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('outputFields')">필드 편집</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('outputFields')">예제 넣기</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('outputFields')">복사</button>
</div>
</div>
@@ -1070,7 +1068,7 @@
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label">HTTP API Name (Glow api-list)</label>
<input type="text" class="form-control" name="httpApiName" pattern="^[a-zA-Z0-9_-]+$" placeholder="비우ë©?Tool ?´ë¦„?¼ë¡œ ?<3F>ë<EFBFBD>™ ?<3F>성">
<input type="text" class="form-control" name="httpApiName" pattern="^[a-zA-Z0-9_-]+$" placeholder="비우면 Tool 이름으로 자동 생성">
<div class="input-hint">HTTP only. Must match glow.communication.http.api-list[].name; URL and method are read from Glow YAML.</div>
</div>
<div class="col-md-6 mt-3 mt-md-0">
@@ -1102,12 +1100,12 @@
<div class="docgen-workspace">
<section class="docgen-config">
<h2 class="docgen-section-title">Document Generator</h2>
<p class="docgen-section-copy">Tool Metadataë¥?기ì??¼ë¡œ 개발 ?°ì¶œë¬?Excel???<3F>성?©ë‹ˆ?? ? íƒ<C3AD>??문서ë§??Œì<C592>¼ê³??œíЏ???¬í•¨?©ë‹ˆ??</p>
<p class="docgen-section-copy">Tool Metadata를 기준으로 개발 산출물 Excel을 생성합니다. 선택된 문서만 파일과 시트에 포함됩니다.</p>
<div class="docgen-field">
<label class="docgen-field-label" for="docgenToolSelect">Tool</label>
<select id="docgenToolSelect" disabled><option>Tool Metadataë¥?불러?¤ëŠ” ì¤?..</option></select>
<div id="docgenGrowToolHint" class="docgen-hint">Gateway??ToolMetadata 목ë¡<EFBFBD>??조회?˜ê³  ?ˆìе?ˆë‹¤.</div>
<select id="docgenToolSelect" disabled><option>Tool Metadata를 불러오는 중...</option></select>
<div id="docgenGrowToolHint" class="docgen-hint">GatewayToolMetadata 목록을 조회하고 있습니다.</div>
</div>
<div class="docgen-two-col docgen-field">
@@ -1124,10 +1122,10 @@
<div class="docgen-field">
<label class="docgen-field-label">Included Sheets</label>
<div class="docgen-check-grid">
<label class="docgen-check-card"><input id="docgenProgramCheck" type="checkbox" checked><span>?„로그램?•ì<E280A2>˜??/span></label>
<label class="docgen-check-card"><input id="docgenProcessCheck" type="checkbox" checked><span>처리?¤ê³„</span></label>
<label class="docgen-check-card"><input id="docgenInterfaceCheck" type="checkbox" checked><span>?¸í„°?˜ì<CB9C>´??/span><span class="docgen-mini-badge">별ë<EFBFBD>„ ?Œì<C592>¼</span></label>
<label class="docgen-check-card"><input id="docgenRevisionCheck" type="checkbox" checked><span>개정?´ë ¥</span></label>
<label class="docgen-check-card"><input id="docgenProgramCheck" type="checkbox" checked><span>프로그램정의서</span></label>
<label class="docgen-check-card"><input id="docgenProcessCheck" type="checkbox" checked><span>처리설계</span></label>
<label class="docgen-check-card"><input id="docgenInterfaceCheck" type="checkbox" checked><span>인터페이스</span><span class="docgen-mini-badge">별도 파일</span></label>
<label class="docgen-check-card"><input id="docgenRevisionCheck" type="checkbox" checked><span>개정이력</span></label>
</div>
</div>
@@ -1142,7 +1140,7 @@
<div class="docgen-meta-title">
<div>
<h2 id="docgenToolTitle" class="docgen-loading">Tool Metadata loading...</h2>
<p id="docgenToolDescription">?±ë¡<C3AB>??Tool 목ë¡<C3AB>??ê°€?¸ì˜¤??중입?ˆë‹¤.</p>
<p id="docgenToolDescription">등록된 Tool 목록을 가져오는 중입니다.</p>
</div>
<span id="docgenMetadataStatus" class="docgen-status">SYNC</span>
</div>
@@ -1154,16 +1152,16 @@
</div>
<div class="docgen-list">
<div id="docgenProgramRow" class="docgen-row docgen-enabled"><span class="docgen-number">01</span><div><div class="docgen-name">?„로그램?•ì<E280A2>˜??/div><div class="docgen-description">Basic information · Runtime · Process summary</div></div><span class="docgen-mode docgen-auto">AUTO</span></div>
<div id="docgenProcessRow" class="docgen-row docgen-enabled"><span class="docgen-number">02</span><div><div class="docgen-name">처리?¤ê³„</div><div class="docgen-description">Tool metadata + common design</div></div><span class="docgen-mode docgen-mixed">MIXED</span></div>
<div id="docgenInterfaceRow" class="docgen-row docgen-enabled"><span class="docgen-number">03</span><div><div class="docgen-name">?¸í„°?˜ì<CB9C>´??/div><div class="docgen-description">Request In · Response Out · 2 sheets</div></div><span class="docgen-mode docgen-separate">2 SHEETS · SEPARATE</span></div>
<div id="docgenRevisionRow" class="docgen-row docgen-enabled"><span class="docgen-number">04</span><div><div class="docgen-name">개정?´ë ¥</div><div class="docgen-description">Version · Date · Change information</div></div><span class="docgen-mode">MANUAL</span></div>
<div id="docgenProgramRow" class="docgen-row docgen-enabled"><span class="docgen-number">01</span><div><div class="docgen-name">프로그램정의서</div><div class="docgen-description">Basic information · Runtime · Process summary</div></div><span class="docgen-mode docgen-auto">AUTO</span></div>
<div id="docgenProcessRow" class="docgen-row docgen-enabled"><span class="docgen-number">02</span><div><div class="docgen-name">처리설계</div><div class="docgen-description">Tool metadata + common design</div></div><span class="docgen-mode docgen-mixed">MIXED</span></div>
<div id="docgenInterfaceRow" class="docgen-row docgen-enabled"><span class="docgen-number">03</span><div><div class="docgen-name">인터페이스</div><div class="docgen-description">Request In · Response Out · 2 sheets</div></div><span class="docgen-mode docgen-separate">2 SHEETS · SEPARATE</span></div>
<div id="docgenRevisionRow" class="docgen-row docgen-enabled"><span class="docgen-number">04</span><div><div class="docgen-name">개정이력</div><div class="docgen-description">Version · Date · Change information</div></div><span class="docgen-mode">MANUAL</span></div>
</div>
<p class="docgen-legend">AUTO: Tool metadata · MIXED: Metadata + common design · MANUAL: Review required<br>Output Files · ?¸í„°?˜ì<CB9C>´???•ì<E280A2>˜?œëŠ” 별ë<E2809E>„ Excel ?Œì<C592>¼ë¡??<3F>성?©ë‹ˆ??</p>
<p class="docgen-legend">AUTO: Tool metadata · MIXED: Metadata + common design · MANUAL: Review required<br>Output Files · 인터페이스 정의서는 별도 Excel 파일로 생성됩니다.</p>
<div class="docgen-files">
<div id="docgenProgramFile" class="docgen-file"><div class="docgen-file-kind">PROGRAM SPEC</div><div id="docgenProgramFileName" class="docgen-file-name">tool_?„로그램?•ì<E280A2>˜??v1.0_YYYYMMDD.xlsx</div></div>
<div id="docgenInterfaceFile" class="docgen-file"><div class="docgen-file-kind">INTERFACE SPEC · REQUEST IN / RESPONSE OUT</div><div id="docgenInterfaceFileName" class="docgen-file-name">tool_?¸í„°?˜ì<CB9C>´?¤ì •?˜ì„œ_v1.0_YYYYMMDD.xlsx</div></div>
<div id="docgenProgramFile" class="docgen-file"><div class="docgen-file-kind">PROGRAM SPEC</div><div id="docgenProgramFileName" class="docgen-file-name">tool_프로그램정의서_v1.0_YYYYMMDD.xlsx</div></div>
<div id="docgenInterfaceFile" class="docgen-file"><div class="docgen-file-kind">INTERFACE SPEC · REQUEST IN / RESPONSE OUT</div><div id="docgenInterfaceFileName" class="docgen-file-name">tool_인터페이스정의서_v1.0_YYYYMMDD.xlsx</div></div>
</div>
</section>
</div>
@@ -1216,26 +1214,26 @@
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="fieldEditorModalLabel">?„드 ?¸ì§‘</h5>
<h5 class="modal-title" id="fieldEditorModalLabel">필드 편집</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="input-hint mt-0 mb-3">?‰ì<E280B0>„ ì¶”ê????„드ë¥??…ë ¥?˜ë©´ ?€?¥í•  ??JSON?¼ë¡œ ?<3F>ë<EFBFBD>™ ë³€?˜ë<CB9C>©?ˆë‹¤.</p>
<p class="input-hint mt-0 mb-3">행을 추가해 필드를 입력하면 저장할 때 JSON으로 자동 변환됩니다.</p>
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
<span class="input-hint mt-0 mb-0">빠른 ?…ë ¥:</span>
<span class="input-hint mt-0 mb-0">빠른 입력:</span>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('customer')">고객조회</button>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('list')">목록조회</button>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('detail')">?¨ê±´ì¡°íšŒ</button>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('write')">?±ë¡<C3AB>/ë³€ê²?/button>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('clear')">초기??/button>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('detail')">단건조회</button>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('write')">등록/변경</button>
<button type="button" class="btn-secondary-action" onclick="applyFieldTemplate('clear')">초기화</button>
</div>
<div class="mb-4">
<label class="form-label">?<3F>연??초안 만들�/label>
<label class="form-label">자연어 초안 만들기</label>
<div class="input-group">
<input id="fieldNaturalLanguage" type="text" class="form-control" placeholder="?? ê³ ê°<C3AA>번호ë¡?계약 ?<3F>태ë¥?조회">
<button id="fieldDraftButton" type="button" class="btn-action" onclick="createNaturalFieldDraft()">AI 초안 만들�/button>
<input id="fieldNaturalLanguage" type="text" class="form-control" placeholder="예: 고객번호로 계약 상태를 조회">
<button id="fieldDraftButton" type="button" class="btn-action" onclick="createNaturalFieldDraft()">AI 초안 만들기</button>
</div>
<div class="input-hint">Chat ?”ë©´ê³?ê°™ì? AI 모ë<C2A8>¸??문장??ë¶„ì„<C3AC>???„재 Input ?<3F>는 Output ?„드 초안??만듭?ˆë‹¤.</div>
<div class="input-hint">Chat 화면과 같은 AI 모델이 문장을 분석해 현재 Input 또는 Output 필드 초안을 만듭니다.</div>
</div>
<div class="table-responsive">
<table class="table align-middle" id="fieldEditorTable">
@@ -1247,15 +1245,15 @@
<th style="min-width: 260px;">Object list item fields (JSON)</th>
<th style="min-width: 210px;">Description</th>
<th style="min-width: 150px;">Pattern (Regex)</th>
<th style="min-width: 180px;">Examples (Enter ?¨ìœ„ 구분)</th>
<th style="min-width: 180px;">Examples (Enter 단위 구분)</th>
<th style="min-width: 125px;">Required</th>
<th style="width: 1%;">?? œ</th>
<th style="width: 1%;">삭제</th>
</tr>
</thead>
<tbody id="fieldEditorBody"></tbody>
</table>
</div>
<button type="button" class="btn-secondary-action" onclick="addFieldEditorRow()">+ ?„드 ì¶”ê?</button>
<button type="button" class="btn-secondary-action" onclick="addFieldEditorRow()">+ 필드 추가</button>
<div class="mt-4">
<label class="form-label">JSON 미리보기</label>
<pre id="fieldEditorPreview" class="mb-0 p-3 rounded" style="background:#09090b; border:1px solid #27272a; color:#e4e4e7; font-size:0.78rem; max-height:190px; overflow:auto;"></pre>
@@ -1263,7 +1261,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary-action" data-bs-dismiss="modal" style="padding: 0.5rem 1rem; font-size: 0.875rem;">취소</button>
<button type="button" class="btn-action" onclick="applyFieldEditor()">JSON???<3F>ìš©</button>
<button type="button" class="btn-action" onclick="applyFieldEditor()">JSON에 적용</button>
</div>
</div>
</div>
@@ -1274,7 +1272,7 @@
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">?˜ìœ„ ?„드(itemFields) ?¸ì§‘</h5>
<h5 class="modal-title">하위 필드(itemFields) 편집</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -1295,7 +1293,7 @@
<tbody id="subFieldEditorBody"></tbody>
</table>
</div>
<button type="button" class="btn-action mt-2" onclick="addSubFieldEditorRow()">+ ?˜ìœ„ ?„드 ì¶”ê?</button>
<button type="button" class="btn-action mt-2" onclick="addSubFieldEditorRow()">+ 하위 필드 추가</button>
<div class="mt-4">
<label class="form-label">JSON 미리보기</label>
<pre id="subFieldEditorPreview" class="mb-0 p-3 rounded" style="background:#09090b; border:1px solid #27272a; color:#e4e4e7; font-size:0.78rem; max-height:190px; overflow:auto;"></pre>
@@ -1303,7 +1301,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary-action" data-bs-dismiss="modal">취소</button>
<button type="button" class="btn-action" onclick="applySubFieldEditor()">?<3F>ìš©</button>
<button type="button" class="btn-action" onclick="applySubFieldEditor()">적용</button>
</div>
</div>
</div>
@@ -1388,13 +1386,13 @@
const button = dg('docgenGenerateBtn');
select.disabled = true;
button.disabled = true;
dg('docgenGrowToolHint').textContent = 'Gateway??ToolMetadata 목ë¡<EFBFBD>??조회?˜ê³  ?ˆìе?ˆë‹¤.';
dg('docgenGrowToolHint').textContent = 'GatewayToolMetadata 목록을 조회하고 있습니다.';
try {
const response = await fetch('/mcp/api/v1/tools/list', { cache: 'no-store' });
if (!response.ok) throw new Error(`Gateway HTTP ${response.status}`);
const payload = await response.json();
const tools = payload?.result?.tools;
if (!Array.isArray(tools)) throw new Error('ToolMetadata ?‘답 ?•ì<C3AC>???¬ë°”르ì? ?Šìе?ˆë‹¤.');
if (!Array.isArray(tools)) throw new Error('ToolMetadata 응답 형식이 올바르지 않습니다.');
state.tools = tools.slice().sort((a, b) =>
(a.categoryKey || '').localeCompare(b.categoryKey || '') ||
(a.name || '').localeCompare(b.name || '')
@@ -1403,19 +1401,19 @@
`<option value="${index}">[${escapeHtml(tool.categoryKey || 'etc')}] ${escapeHtml(tool.name || tool.displayName || tool.uid)}</option>`
).join('');
select.disabled = state.tools.length === 0;
dg('docgenGrowToolHint').textContent = `ToolMetadata???±ë¡<C3AB>??${state.tools.length}ê°?Tool??모ë<C3AB> ?œì‹œ?©ë‹ˆ??`;
dg('docgenGrowToolHint').textContent = `ToolMetadata에 등록된 ${state.tools.length}Tool을 모두 표시합니다.`;
if (state.tools.length) {
select.value = '0';
selectTool();
} else {
showMetadataError('?±ë¡<C3AB>??Tool???†ìе?ˆë‹¤.');
showMetadataError('등록된 Tool이 없습니다.');
}
} catch (error) {
state.tools = [];
state.selected = null;
showMetadataError(error.message);
select.innerHTML = '<option>Tool Metadata 조회 ?¤íŒ¨</option>';
dg('docgenGrowToolHint').innerHTML = `<span class="docgen-error">${escapeHtml(error.message)}</span> · Gateway ?¤í–‰ ?<3F>태ë¥??•ì<E280A2>¸?˜ì„¸??`;
select.innerHTML = '<option>Tool Metadata 조회 실패</option>';
dg('docgenGrowToolHint').innerHTML = `<span class="docgen-error">${escapeHtml(error.message)}</span> · Gateway 실행 상태를 확인하세요.`;
}
}
@@ -1426,7 +1424,7 @@
dg('docgenVersion').value = normalizeVersion(tool.semver || dg('docgenVersion').value || '1.0');
dg('docgenToolTitle').classList.remove('docgen-loading', 'docgen-error');
dg('docgenToolTitle').textContent = tool.displayName || tool.name;
dg('docgenToolDescription').textContent = tool.description || '?¤ëª…???±ë¡<C3AB>?˜ì? ?Šì? Tool?…니??';
dg('docgenToolDescription').textContent = tool.description || '설명이 등록되지 않은 Tool입니다.';
dg('docgenCategoryTag').textContent = `Category · ${tool.categoryKey || '-'}`;
dg('docgenInterfaceTag').textContent = `Interface · ${tool.integrationType || '-'}`;
dg('docgenMetadataStatus').textContent = tool.enabled === false ? 'DISABLED' : 'READY';
@@ -1448,14 +1446,14 @@
const hasProgram = checks.program.checked || checks.process.checked || checks.revision.checked;
dg('docgenProgramFile').classList.toggle('docgen-disabled', !hasProgram);
dg('docgenInterfaceFile').classList.toggle('docgen-disabled', !checks.interface.checked);
dg('docgenProgramFileName').textContent = `${toolName}_?„로그램?•ì<E280A2>˜??v${version}_${date}.xlsx`;
dg('docgenInterfaceFileName').textContent = `${toolName}_?¸í„°?˜ì<CB9C>´?¤ì •?˜ì„œ_v${version}_${date}.xlsx`;
dg('docgenProgramFileName').textContent = `${toolName}_프로그램정의서_v${version}_${date}.xlsx`;
dg('docgenInterfaceFileName').textContent = `${toolName}_인터페이스정의서_v${version}_${date}.xlsx`;
dg('docgenGenerateBtn').disabled = !state.selected || (!hasProgram && !checks.interface.checked);
}
async function generateFiles() {
const tool = state.selected;
if (!tool) return notify('?<3F>성??Tool??? íƒ<C3AD>?˜ì„¸??');
if (!tool) return notify('생성할 Tool을 선택하세요.');
const button = dg('docgenGenerateBtn');
const version = safeVersion(dg('docgenVersion').value);
const date = yyyymmdd();
@@ -1474,13 +1472,13 @@
try {
if (hasProgram) {
await requestDownload('/mcp/api/v1/admin/documents/program', request,
`${toolName}_?„로그램?•ì<E280A2>˜??v${version}_${date}.xlsx`);
`${toolName}_프로그램정의서_v${version}_${date}.xlsx`);
}
if (checks.interface.checked) {
await requestDownload('/mcp/api/v1/admin/documents/interface', request,
`${toolName}_?¸í„°?˜ì<CB9C>´?¤ì •?˜ì„œ_v${version}_${date}.xlsx`);
`${toolName}_인터페이스정의서_v${version}_${date}.xlsx`);
}
notify('? íƒ<C3AD>??Excel 문서 ?¤ìš´ë¡œë“œë¥??„료?ˆìе?ˆë‹¤.');
notify('선택한 Excel 문서 다운로드를 완료했습니다.');
} catch (error) {
notify(error.message);
} finally {
@@ -1496,12 +1494,12 @@
body: JSON.stringify(payload)
});
if (!response.ok) {
let message = `문서 ?<3F>성???¤íŒ¨?ˆìе?ˆë‹¤. (HTTP ${response.status})`;
let message = `문서 생성에 실패했습니다. (HTTP ${response.status})`;
try {
const error = await response.json();
if (error?.error) message = error.error;
} catch (_) {
// JSON ?¤ë¥˜ ?‘답???„닌 경우 기본 메시지ë¥??¬ìš©?©ë‹ˆ??
// JSON 오류 응답이 아닌 경우 기본 메시지를 사용합니다.
}
throw new Error(message);
}
@@ -1562,7 +1560,7 @@
const resultBox = document.getElementById('resultBox');
resultBox.style.display = 'block';
if (result.includes('[?¤ë¥˜]') || result.includes('?¤ë¥˜ ë°œìƒ<C3AC>:')) {
if (result.includes('[오류]') || result.includes('오류 발생:')) {
resultBox.className = 'error';
resultBox.textContent = result;
} else {
@@ -1628,16 +1626,16 @@
const form = document.getElementById('toolForm');
const data = Object.fromEntries(new FormData(form).entries());
if (!data.baseName) {
throw new Error('Base Name???…ë ¥?´ì£¼?¸ìš”.');
throw new Error('Base Name을 입력해주세요.');
}
if (data.routingType === 'MCI' && (!data.interfaceId || !data.clientSystemCode)) {
throw new Error('MCI Tool?€ Legacy Interface ID?€ Target System Codeë¥??…ë ¥?´ì•¼ ?©ë‹ˆ??');
throw new Error('MCI Tool Legacy Interface ID Target System Code를 입력해야 합니다.');
}
if (data.routingType === 'HTTP' && !data.httpApiName) {
throw new Error('HTTP Tool?€ HTTP API Name???…ë ¥?´ì•¼ ?©ë‹ˆ??');
throw new Error('HTTP Tool HTTP API Name을 입력해야 합니다.');
}
if (!['MCI', 'HTTP'].includes(data.routingType)) {
throw new Error('?¬ëŸ¬ Tool UseCase??MCI ?<3F>는 HTTP ?„로? ì½œë§?ì§€?<3F>í•©?ˆë‹¤.');
throw new Error('여러 Tool UseCaseMCI 또는 HTTP 프로토콜만 지원합니다.');
}
return {
baseName: data.baseName,
@@ -1665,7 +1663,7 @@
try {
const tool = currentToolDefinition();
if (groupedTools.some(item => item.methodName === tool.methodName || item.baseName === tool.baseName)) {
throw new Error('ê°™ì? Base Name ?<3F>는 메서?œëª…???´ë? 묶ì<C2B6>Œ???ˆìе?ˆë‹¤.');
throw new Error('같은 Base Name 또는 메서드명이 이미 묶음에 있습니다.');
}
groupedTools.push(tool);
renderToolGroupSummary();
@@ -1679,13 +1677,13 @@
const workspacePath = document.querySelector('#toolForm input[name="workspacePath"]')?.value || document.querySelector('input[name="workspacePath"]')?.value || '';
const categoryKey = document.querySelector('#toolForm [name="categoryKey"]').value.trim();
const select = document.getElementById('toolGroupUseCaseSelect');
select.innerHTML = '<option value="">??UseCase ?<3F>성</option>';
select.innerHTML = '<option value="">UseCase 생성</option>';
if (!/^[a-z0-9]{3}$/.test(categoryKey)) return;
try {
const response = await fetch(`/api/v1/scaffold/usecases?moduleName=${encodeURIComponent(moduleName)}&categoryKey=${encodeURIComponent(categoryKey)}&workspacePath=${encodeURIComponent(workspacePath)}`);
if (!response.ok) throw new Error('UseCase 목ë¡<EFBFBD> 조회 ?¤íŒ¨');
if (!response.ok) throw new Error('UseCase 목록 조회 실패');
const useCases = await response.json();
useCases.forEach(useCaseName => select.add(new Option(`기존 ${useCaseName}???¨ìˆ˜ ì¶”ê?`, useCaseName)));
useCases.forEach(useCaseName => select.add(new Option(`기존 ${useCaseName}에 함수 추가`, useCaseName)));
} catch (error) {
console.warn(error);
}
@@ -1696,12 +1694,12 @@
const workspacePath = document.querySelector('#toolForm input[name="workspacePath"]')?.value || document.querySelector('input[name="workspacePath"]')?.value || '';
const select = document.getElementById('domainCategorySelect');
const nameInput = document.getElementById('domainCategoryName');
select.innerHTML = '<option value="">???„ë©”???<3F>성</option>';
select.innerHTML = '<option value="">새 도메인 생성</option>';
try {
const response = await fetch(`/api/v1/scaffold/categories?moduleName=${encodeURIComponent(moduleName)}&workspacePath=${encodeURIComponent(workspacePath)}`);
if (!response.ok) throw new Error('Category 목ë¡<EFBFBD> 조회 ?¤íŒ¨');
if (!response.ok) throw new Error('Category 목록 조회 실패');
const categories = await response.json();
categories.forEach(cat => select.add(new Option(`기존 ${cat} ?„ë©”??, cat)));
categories.forEach(cat => select.add(new Option(`기존 ${cat} 도메인`, cat)));
} catch (error) {
console.warn(error);
}
@@ -1737,8 +1735,8 @@
function renderToolGroupSummary() {
const summary = document.getElementById('toolGroupSummary');
summary.textContent = groupedTools.length
? `${groupedTools.length}ê°?Tool: ${groupedTools.map(tool => `${tool.baseName} ??${tool.methodName}()`).join(', ')}`
: '묶ì<C2B6>Œ??ì¶”ê???Tool ?†ì<E280A0>Œ ???¼ë°˜ Generate Tool?€ 기존 ?¨ì<C2A8>¼ Tool ?<3F>성?…니??';
? `${groupedTools.length}개 Tool: ${groupedTools.map(tool => `${tool.baseName} ${tool.methodName}()`).join(', ')}`
: '묶음에 추가된 Tool 없음 — 일반 Generate Tool은 기존 단일 Tool 생성입니다.';
}
document.getElementById('toolForm').addEventListener('submit', function(e) {
@@ -1747,7 +1745,7 @@
e.preventDefault();
e.stopImmediatePropagation();
if (!/^[A-Z][A-Za-z0-9]*$/.test(useCaseName)) {
alert('UseCase ?´ë¦„?€ PascalCaseë¡??…ë ¥?´ì£¼?¸ìš”. ?? Customer');
alert('UseCase 이름은 PascalCase로 입력해주세요. 예: Customer');
return;
}
try {
@@ -1757,7 +1755,7 @@
const btn = this.querySelector('button[type="submit"]');
const originalText = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 처리�..';
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 처리중...';
btn.disabled = true;
fetch('/api/v1/scaffold/tool-group', {
@@ -1962,15 +1960,15 @@
fields = JSON.parse(textarea.value);
if (!Array.isArray(fields)) throw new Error('not an array');
} catch (error) {
alert('?„재 JSON ?•ì<C3AC>???¬ë°”르ì? ?Šìе?ˆë‹¤. JSON???˜ì •?????¤ì‹œ ?´ì–´ì£¼ì„¸??');
alert('현재 JSON 형식이 올바르지 않습니다. JSON을 수정한 후 다시 열어주세요.');
return;
}
}
fieldEditorTargetId = targetId;
document.getElementById('fieldEditorModalLabel').textContent = targetId === 'inputFields'
? 'Input Fields ?¸ì§‘'
: 'Output Fields ?¸ì§‘';
? 'Input Fields 편집'
: 'Output Fields 편집';
const body = document.getElementById('fieldEditorBody');
body.replaceChildren();
(fields.length ? fields : [{}]).forEach(addFieldEditorRow);
@@ -2041,14 +2039,14 @@
itemFieldsContainer.className = 'd-flex gap-1';
const itemFieldsInput = makeInput(field.type === 'List' && field.itemType === 'Object'
? JSON.stringify(field.itemFields || []) : '', 'JSON ì§<C3AC>ì ?…ë ¥ ?<3F>는 ?°ì¸¡ ?¸ì§‘ ?´ë¦­');
? JSON.stringify(field.itemFields || []) : '', 'JSON 직접입력 또는 우측 편집 클릭');
itemFieldsInput.dataset.field = 'itemFields';
itemFieldsInput.style.flex = '1';
const editItemBtn = document.createElement('button');
editItemBtn.type = 'button';
editItemBtn.className = 'btn-secondary-action';
editItemBtn.textContent = '?¸ì§‘';
editItemBtn.textContent = '편집';
editItemBtn.style.padding = '0.2rem 0.5rem';
editItemBtn.onclick = () => {
if (typeof openSubFieldEditor === 'function') openSubFieldEditor(itemFieldsInput);
@@ -2062,7 +2060,7 @@
itemFieldsInput.disabled = !objectList;
editItemBtn.disabled = !objectList;
itemFieldsInput.placeholder = objectList
? 'JSON ì§<EFBFBD>ì ?…ë ¥ ?<3F>는 ?°ì¸¡ ?¸ì§‘ ?´ë¦­'
? 'JSON 직접입력 또는 우측 편집 클릭'
: 'Select List > Object to enter item fields';
if (!objectList) itemFieldsInput.value = '';
};
@@ -2098,7 +2096,7 @@
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'btn-secondary-action';
deleteButton.textContent = '?? œ';
deleteButton.textContent = '삭제';
deleteButton.addEventListener('click', () => {
row.remove();
updateFieldEditorPreview();
@@ -2156,12 +2154,12 @@
async function createNaturalFieldDraft() {
const source = document.getElementById('fieldNaturalLanguage').value.trim();
if (!source) {
alert('?„드 초안??만들 문장???…ë ¥?´ì£¼?¸ìš”.');
alert('필드 초안을 만들 문장을 입력해주세요.');
return;
}
const button = document.getElementById('fieldDraftButton');
const originalLabel = button.textContent;
button.textContent = 'AI ?<3F>성 �..';
button.textContent = 'AI 생성 중...';
button.disabled = true;
try {
const response = await fetch('/api/v1/scaffold/field-draft', {
@@ -2170,16 +2168,16 @@
body: JSON.stringify({ description: source, target: fieldEditorTargetId, model: selectedAiModel() })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'AI 초안 ?<3F>성???¤íŒ¨?ˆìе?ˆë‹¤.');
if (!response.ok) throw new Error(result.error || 'AI 초안 생성에 실패했습니다.');
if (!Array.isArray(result.fields) || result.fields.length === 0) {
throw new Error('AIê°€ ?¬ìš©?????ˆëŠ” ?„드ë¥?만들지 못했?µë‹ˆ??');
throw new Error('AI가 사용할 수 있는 필드를 만들지 못했습니다.');
}
const body = document.getElementById('fieldEditorBody');
body.replaceChildren();
result.fields.forEach(addFieldEditorRow);
updateFieldEditorPreview();
} catch (error) {
alert(`AI ì´ˆìˆ ?<EFBFBD>ì± ?¤íŒ¨: ${error.message}`);
alert(`AI 초안 생성 실패: ${error.message}`);
} finally {
button.textContent = originalLabel;
button.disabled = false;
@@ -2193,12 +2191,12 @@
async function createAiToolDraft() {
const source = document.getElementById('toolNaturalLanguage').value.trim();
if (!source) {
alert('AIê°€ 만들 Tool ?¤ëª…???…ë ¥?´ì£¼?¸ìš”.');
alert('AI가 만들 Tool 설명을 입력해주세요.');
return;
}
const button = document.getElementById('toolDraftButton');
const originalLabel = button.textContent;
button.textContent = 'AI ?<3F>성 �..';
button.textContent = 'AI 생성 중...';
button.disabled = true;
try {
const response = await fetch('/api/v1/scaffold/tool-draft', {
@@ -2207,7 +2205,7 @@
body: JSON.stringify({ description: source, model: selectedAiModel() })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'AI Tool 초안 ?<3F>성???¤íŒ¨?ˆìе?ˆë‹¤.');
if (!response.ok) throw new Error(result.error || 'AI Tool 초안 생성에 실패했습니다.');
const form = document.getElementById('toolForm');
form.elements.baseName.value = result.baseName || '';
@@ -2231,9 +2229,9 @@
form.elements.ownerOrg.value = result.ownerOrg || 'MCP_TOOL';
document.getElementById('inputFields').value = JSON.stringify(result.inputFields || [], null, 2);
document.getElementById('outputFields').value = JSON.stringify(result.outputFields || [], null, 2);
alert('Tool 초안??채웠?µë‹ˆ?? Legacy Interface ID?€ Client System Code???¤ì œ ?°ê³„ 명세ë¥??•ì<E280A2>¸???…ë ¥?´ì£¼?¸ìš”.');
alert('Tool 초안을 채웠습니다. Legacy Interface ID Client System Code는 실제 연계 명세를 확인해 입력해주세요.');
} catch (error) {
alert(`AI Tool ì´ˆìˆ ?<EFBFBD>ì± ?¤íŒ¨: ${error.message}`);
alert(`AI Tool 초안 생성 실패: ${error.message}`);
} finally {
button.textContent = originalLabel;
button.disabled = false;
@@ -2246,11 +2244,11 @@
for (const field of fields) {
const name = field.name;
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
alert(`?ëœëª?"${name}"?(?? Java ?ëœëª??ì<EFBFBD>?¼ë¡œ ?ë ¥?´ì£¼?¸ìš.`);
alert(`필드명 "${name}"은(는) Java 필드명 형식으로 입력해주세요.`);
return;
}
if (names.has(name)) {
alert(`?ëœëª?"${name}"??ê°) ì¤ë³µ?˜ìˆ?µëˆ??`);
alert(`필드명 "${name}"이(가) 중복되었습니다.`);
return;
}
names.add(name);
@@ -2379,7 +2377,7 @@
})
.then(response => response.text())
.then(result => {
if (result.includes('[?¤ë¥˜]') || result.includes('?¤ë¥˜ ë°œìƒ<C3AC>:')) {
if (result.includes('[오류]') || result.includes('오류 발생:')) {
alert('Error updating source:\n' + result);
} else {
alert('Source code updated successfully.\nPlease rebuild the Pod container to reflect changes.');

View File

@@ -207,7 +207,7 @@
<svg class="w-3 h-3 text-zinc-500" fill="currentColor" viewBox="0 0 20 20"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zM9 9a1 1 0 012 0v4a1 1 0 11-2 0V9zm1-5a1 1 0 110 2 1 1 0 010-2z"></path></svg>
Auto-saved to LocalStorage
</div>
<button onclick="resetPayload()" class="btn-sm py-2 px-4 text-red-400 hover:text-red-300 border border-red-500/30 hover:border-red-400 mr-2" style="font-size:12px;" title="?€?¥ë<C2A5>œ ?˜ì<CB9C>´ë¡œë“œë¥??? œ?˜ê³  ?¤í‚¤ë§?기본값으ë¡?초기?”í•©?ˆë‹¤">Reset to Schema</button>
<button onclick="resetPayload()" class="btn-sm py-2 px-4 text-red-400 hover:text-red-300 border border-red-500/30 hover:border-red-400 mr-2" style="font-size:12px;" title="저장된 페이로드를 삭제하고 스키마 기본값으로 초기화합니다">Reset to Schema</button>
<button onclick="closePayloadModal()" class="btn-sm py-2 px-4">Cancel</button>
<button onclick="savePayload()" class="btn-execute py-2 px-4" style="font-size:12px;">Save & Apply</button>
</div>
@@ -369,42 +369,24 @@
localStorage.setItem('mcp_tester_payloads', JSON.stringify(dict));
}
function generateDummyForSchema(schema) {
if (!schema) return null;
if (schema.example !== undefined) return schema.example;
if (schema.examples && Array.isArray(schema.examples) && schema.examples.length > 0) return schema.examples[0];
if (schema.default !== undefined) return schema.default;
if (schema.type === 'object') {
const obj = {};
if (schema.properties) {
for (const [k, v] of Object.entries(schema.properties)) {
obj[k] = generateDummyForSchema(v);
}
}
return obj;
} else if (schema.type === 'array') {
return [generateDummyForSchema(schema.items || {})];
} else if (schema.type === 'string') {
if (schema.enum && schema.enum.length > 0) return schema.enum[0];
return "test_string";
} else if (schema.type === 'integer' || schema.type === 'number') {
return schema.minimum !== undefined ? schema.minimum : 1;
} else if (schema.type === 'boolean') {
return true;
}
return null;
}
function generateDummyPayload(tool) {
if (tool._customPayload) return tool._customPayload;
const schema = tool.inputSchema || (tool.inputSchema || tool.parametersSchema);
const schema = tool.parametersSchema;
if (!schema || !schema.properties) return {};
const payload = {};
for (const [key, value] of Object.entries(schema.properties)) {
payload[key] = generateDummyForSchema(value);
if (value.example !== undefined) payload[key] = value.example;
else if (value.examples && Array.isArray(value.examples) && value.examples.length > 0) payload[key] = value.examples[0];
else if (value.default !== undefined) payload[key] = value.default;
else {
if (value.type === 'string') {
if (value.enum && value.enum.length > 0) payload[key] = value.enum[0];
else payload[key] = "test_string";
} else if (value.type === 'integer' || value.type === 'number') payload[key] = value.minimum !== undefined ? value.minimum : 1;
else if (value.type === 'boolean') payload[key] = true;
else payload[key] = null;
}
}
return payload;
}
@@ -498,9 +480,9 @@
tool._responseData = res.data;
if (tool._isFailed) {
appendLog(`??[ERROR] ${tool.name} failed (${res.status}) - ${res.latency}ms`, 'error');
appendLog(`[ERROR] ${tool.name} failed (${res.status}) - ${res.latency}ms`, 'error');
} else {
appendLog(`??[SUCCESS] ${tool.name} completed - ${res.latency}ms`, 'success');
appendLog(`[SUCCESS] ${tool.name} completed - ${res.latency}ms`, 'success');
}
// Update UI if visible
@@ -514,7 +496,6 @@
}
async function executeRawCall(name, payload) {
// Auto-coerce payload types based on schema to prevent JSON validation errors
const tool = allTools.find(t => t.name === name);
if (tool && tool.inputSchema && tool.inputSchema.properties) {
const props = tool.inputSchema.properties;
@@ -780,7 +761,7 @@ async function executeRawCall(name, payload) {
}
function renderSmartForm(tool, payloadToUse = null) {
const schema = (tool.inputSchema || tool.parametersSchema);
const schema = tool.parametersSchema;
const form = document.getElementById('payloadFormContainer');
form.innerHTML = '';
@@ -868,18 +849,18 @@ async function executeRawCall(name, payload) {
if (currentEditIndex === -1) return;
const tool = allTools[currentEditIndex];
// 로컬 ?¤í† ë¦¬ì??<3F>서 ?? œ
// 로컬 스토리지에서 삭제
const savedPayloads = JSON.parse(localStorage.getItem('mcp_tester_payloads') || '{}');
delete savedPayloads[tool.name];
localStorage.setItem('mcp_tester_payloads', JSON.stringify(savedPayloads));
// ?°í????<3F>태 초기??
// 런타임 상태 초기화
tool._customPayload = null;
// ???¤í‚¤ë§?기반 기본 ?˜ì<CB9C>´ë¡œë“œ ?<3F>성
// 새 스키마 기반 기본 페이로드 생성
const freshPayload = generateDummyPayload(tool);
// UI ?…ë<E280A6>°?´íЏ
// UI 업데이트
document.getElementById('payloadEditor').value = JSON.stringify(freshPayload, null, 2);
renderSmartForm(tool, freshPayload);
}
@@ -1082,8 +1063,8 @@ async function executeRawCall(name, payload) {
// Or we just map /mcp/api/v1/tools/call and use tool names as different request bodies,
// but for documentation purposes, mapping them as separate virtual paths is usually better to view in Swagger UI.
currentExportJson.forEach(tool => {
const schemaProps = (tool.inputSchema || tool.parametersSchema)?.properties || {};
const required = (tool.inputSchema || tool.parametersSchema)?.required || [];
const schemaProps = tool.parametersSchema?.properties || {};
const required = tool.parametersSchema?.required || [];
paths[`/mcp/api/v1/tools/call?name=${tool.name}`] = {
post: {
@@ -1167,8 +1148,8 @@ async function executeRawCall(name, payload) {
md += `### \`${tool.name}\`\n\n`;
md += `**Description:** ${tool.description || 'N/A'}\n\n`;
const props = (tool.inputSchema || tool.parametersSchema)?.properties || {};
const reqs = (tool.inputSchema || tool.parametersSchema)?.required || [];
const props = tool.parametersSchema?.properties || {};
const reqs = tool.parametersSchema?.required || [];
if (Object.keys(props).length > 0) {
md += `#### Parameters\n\n`;
@@ -1176,7 +1157,7 @@ async function executeRawCall(name, payload) {
md += `|------|------|----------|-------------|---------|\n`;
Object.keys(props).forEach(p => {
const pData = props[p];
const isReq = reqs.includes(p) ? '??Yes' : '??No';
const isReq = reqs.includes(p) ? 'Yes' : 'No';
let example = pData.example !== undefined ? pData.example : (pData.examples ? pData.examples[0] : '');
if (typeof example === 'object') example = JSON.stringify(example);
md += `| \`${p}\` | \`${pData.type || 'string'}\` | ${isReq} | ${pData.description || ''} | \`${example}\` |\n`;