188 lines
10 KiB
HTML
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>
|