Files
vpd-permission-poc/dds-backoffice/src/main/resources/templates/vector-knowledge.html
2026-07-01 10:08:07 +09:00

188 lines
10 KiB
HTML

<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('지식자료 관리')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<span class="architecture-kicker">DDS · KNOWLEDGE ACCESS</span>
<h1>지식자료 관리</h1>
<p class="context-summary">자료 등록 · 권한 검색</p>
<details class="explanation-details">
<summary>도움말</summary>
<p>문서는 검색 단위로 나뉘고 각 단위에 접근 분류값(<code>TECH_TAG</code>)이 연결됩니다. 역할별 허용 값을 등록하면 보호 규칙이 허용된 분류값만 검색 대상으로 남깁니다.</p>
<p class="mb-0">기본 경로는 Bearer 토큰으로 업무 사용자를 식별합니다. 직접 접근 비교는 고급 검증에서만 사용합니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('search')}"></section>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}"></div>
<div class="alert alert-warning" th:if="${runtimeError}">
벡터 저장소를 확인할 수 없습니다. <span th:text="${runtimeError}"></span>
</div>
<section class="content-band">
<div class="section-heading">
<div>
<h2>자료 등록</h2>
<p class="section-subtitle">제목, 접근 태그, 문서 내용을 입력합니다.</p>
</div>
</div>
<details class="explanation-details">
<summary>도움말</summary>
<p class="mb-0">접근 태그는 이 자료를 볼 수 있는 권한 범위입니다. 권한 관리에 등록된 TAG 값과 일치하는 태그를 입력하세요. 등록 후 문서는 검색 단위로 나뉘고 임베딩이 생성됩니다.</p>
</details>
<form method="post" action="/vector-knowledge/ingest" class="knowledge-entry-form">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<label>자료 제목<input class="form-control" name="title" placeholder="예: DDS 기술 태그 권한 설계" required></label>
<label>접근 태그
<input class="form-control" name="techTags" placeholder="예: SALES, INTERNAL" required>
<span class="form-hint">태그가 하나라도 허용되면 자료를 검색할 수 있습니다.</span>
</label>
<label>문서 본문
<textarea class="form-control" name="content" rows="8" placeholder="문서 내용을 붙여 넣으세요." required></textarea>
</label>
<details class="registration-advanced-options">
<summary>고급 설정</summary>
<p>기본 처리 방식을 변경하거나 기존 자료를 교체할 때만 사용합니다.</p>
<div class="knowledge-options-grid">
<label>검색 단위 길이
<input class="form-control" name="chunkSize" type="number" min="80" max="2000" value="600">
</label>
<label>임베딩 방식
<select class="form-select" name="embeddingMode">
<option value="DEMO">기본 처리</option>
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
</select>
</label>
<label class="full-row">교체할 자료 ID
<input class="form-control" name="documentId" placeholder="기존 등록 결과에 표시된 자료 ID">
<span class="form-hint">입력한 자료의 검색 단위를 새 내용으로 교체합니다.</span>
</label>
<label class="full-row registration-checkbox">
<input type="checkbox" name="replaceExisting" value="true"> 기존 자료를 교체합니다.
</label>
</div>
</details>
<div class="registration-actions"><button class="btn rw-btn-primary" type="submit">등록</button></div>
</form>
<section class="registration-result mt-3" th:if="${ingestResult}" aria-live="polite">
<div>
<strong>등록 완료</strong>
<span>검색할 수 있도록 자료를 처리했습니다.</span>
</div>
<dl>
<div><dt>자료 ID</dt><dd><code th:text="${ingestResult.documentId()}">document</code></dd></div>
<div><dt>검색 단위</dt><dd th:text="${ingestResult.chunkCount() + '개'}">0개</dd></div>
<div><dt>접근 태그</dt><dd th:text="${ingestResult.tagCount() + '개 연결'}">0개 연결</dd></div>
</dl>
</section>
</section>
<section class="content-band">
<div class="section-heading">
<div>
<h2>접근 태그 정책</h2>
<p class="section-subtitle">자료에 입력한 접근 태그의 허용 범위를 관리합니다.</p>
</div>
<div class="d-flex gap-2">
<a class="btn btn-sm rw-btn-secondary" href="/permissions">권한 관리 열기</a>
<a class="btn btn-sm rw-btn-primary" href="/dds-provision">권한 게시</a>
</div>
</div>
<details class="explanation-details mb-0">
<summary>도움말</summary>
<p class="mb-0">예를 들어 SALES 태그를 허용한 역할은 SALES 태그 자료를 검색할 수 있습니다. 여러 태그가 허용되면 그중 하나와 일치하는 자료를 검색합니다. 태그 변경 뒤에는 권한 게시를 실행하세요.</p>
</details>
</section>
<section class="content-band">
<div class="section-heading">
<div>
<h2>권한 검색</h2>
<p class="section-subtitle">임시 Bearer 토큰으로 허용된 자료만 검색합니다.</p>
</div>
</div>
<details class="explanation-details">
<summary>도움말</summary>
<p class="mb-0">토큰은 검색하는 사용자를 확인하기 위한 임시 값이며 저장하지 않습니다. 회수되었거나 만료된 토큰은 사용할 수 없습니다.</p>
</details>
<form hx-post="/vector-knowledge/token-search" hx-target="#vector-token-search-result" hx-swap="innerHTML" class="knowledge-entry-form">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<label>Bearer 토큰
<input class="form-control" name="bearerToken" type="password" autocomplete="off" placeholder="발급된 임시 토큰을 붙여 넣으세요." required>
<span class="form-hint">요청 처리 후 저장하지 않습니다. 회수·만료된 토큰은 거부됩니다.</span>
</label>
<label>검색 질문
<textarea class="form-control" name="query" rows="3" placeholder="예: 세일즈 파이프라인 후속 조치 기준" required></textarea>
</label>
<details class="registration-advanced-options">
<summary>검색 옵션</summary>
<div class="knowledge-options-grid mt-3">
<label>결과 수<input class="form-control" name="limit" type="number" min="1" max="100" value="10"></label>
<label>임베딩 방식
<select class="form-select" name="embeddingMode">
<option value="DEMO">기본 처리</option>
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
</select>
</label>
</div>
</details>
<div class="registration-actions"><button class="btn rw-btn-primary" type="submit">검색</button></div>
</form>
<section id="vector-token-search-result" class="mt-3" aria-live="polite">
<div class="empty-result-guide">토큰과 검색 질문을 입력하면 허용된 자료만 표시합니다.</div>
</section>
</section>
<section class="content-band advanced-workflow">
<details>
<summary>직접 접근 검증</summary>
<p class="section-subtitle">DDS 보호 정책을 점검하는 관리자용 기능</p>
<details class="explanation-details mt-3">
<summary>도움말</summary>
<p class="mb-0">업무 사용자 토큰 경로와 별도로, DDS END USER와 DATA GRANT가 직접 적용되는 결과를 비교합니다. 운영 검색에는 권한 검색을 사용하세요.</p>
</details>
<form hx-post="/vector-knowledge/search" hx-target="#vector-search-result" hx-swap="innerHTML" class="knowledge-entry-form">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<label>검증 사용자
<select class="form-select" name="userKey" required>
<option th:each="user : ${ddsUsers}" th:value="${user.key()}" th:selected="${user.key() == 'both'}"
th:text="${user.label() + ' · ' + user.username()}"></option>
</select>
<span class="form-hint">자격증명은 서버 환경 변수에서만 관리합니다.</span>
</label>
<label>결과 수<input class="form-control" name="limit" type="number" min="1" max="100" value="10"></label>
<label>임베딩 방식
<select class="form-select" name="embeddingMode">
<option value="DEMO">기본 처리</option>
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
</select>
</label>
<label>검색 질문
<textarea class="form-control" name="query" rows="3" placeholder="예: 기술 태그로 지식자료 검색 권한을 제한하는 방법" required></textarea>
</label>
<div class="registration-actions"><button class="btn rw-btn-secondary" type="submit">검증 실행</button></div>
</form>
<section id="vector-search-result" class="mt-3" aria-live="polite">
<div class="empty-result-guide">검증 사용자와 검색 질문을 입력하면 보호 결과를 표시합니다.</div>
</section>
</details>
</section>
<section class="content-band">
<details class="explanation-details">
<summary>저장 현황</summary>
<div class="summary-grid mt-3">
<div class="summary-tile"><span class="label">문서</span><strong th:text="${summary == null ? '-' : summary.documentCount()}">0</strong></div>
<div class="summary-tile"><span class="label">검색 단위</span><strong th:text="${summary == null ? '-' : summary.chunkCount()}">0</strong></div>
<div class="summary-tile"><span class="label">태그 연결</span><strong th:text="${summary == null ? '-' : summary.tagCount()}">0</strong></div>
</div>
</details>
</section>
</main>
</body>
</html>